diff --git a/.gitignore b/.gitignore index c6fdfc4d387f8be1bb3a4f51a73230aabcc9b02b..a76a3a9fa4aa990310cced6e486b1934da9bb80b 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ # typical build dirs build/ bin/ +lib/ target/ _apidoc/ src/doc/development/api/xml/out/ diff --git a/.gitlab/merge_request_templates/Default.md b/.gitlab/merge_request_templates/Default.md index 35c6d01c5904289b77fc7f1de9419ef91a1510e9..3629e0ca3695000863d8c254516f64bf59a7bf60 100644 --- a/.gitlab/merge_request_templates/Default.md +++ b/.gitlab/merge_request_templates/Default.md @@ -28,6 +28,7 @@ guidelines](https://gitlab.com/caosdb/caosdb/-/blob/dev/REVIEW_GUIDELINES.md) - [ ] Up-to-date CHANGELOG.md (or not necessary) - [ ] Up-to-date JSON schema (or not necessary) - [ ] Appropriate user and developer documentation (or not necessary) + - Update / write published documentation (`make doc`). - How do I use the software? Assume "stupid" users. - How do I develop or debug the software? Assume novice developers. - [ ] Annotations in code (Gitlab comments) @@ -41,7 +42,8 @@ guidelines](https://gitlab.com/caosdb/caosdb/-/blob/dev/REVIEW_GUIDELINES.md) - [ ] I understand the intent of this MR - [ ] All automated tests pass - [ ] Up-to-date CHANGELOG.md (or not necessary) -- [ ] Appropriate user and developer documentation (or not necessary) +- [ ] Appropriate user and developer documentation (or not necessary), also in published + documentation. - [ ] The test environment setup works and the intended behavior is reproducible in the test environment - [ ] In-code documentation and comments are up-to-date. diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c5decad3d8be81a49dccdd4a748dc667994c587..01ad67ac0ea431cde6afcceb4d2429ec90559d50 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,10 +5,79 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [Unreleased] ## +## [Unreleased] + +### Added + +### Changed + +### Deprecated + +### Removed + +### Fixed + +### Security + +## [0.12.1] - 2023-12-13 +(Timm Fitschen) + +### Fixed + +* Insufficient permission checks during subproperty filters of SELECT queries + when an entity with retrieve permissions references one without + [linkahead-server#244](https://gitlab.com/linkahead/linkahead-server/-/issues/244) +* Insufficient permission checks in queries when a name of an invisible record + is used in a filter where a visible record references the invisible one + [linkahead-server#242](https://gitlab.com/linkahead/linkahead-server/-/issues/242) + +### Security + +This is an important security patch release. The bugs +[linkahead-server#244](https://gitlab.com/linkahead/linkahead-server/-/issues/244) +and +[linkahead-server#242](https://gitlab.com/linkahead/linkahead-server/-/issues/242) +possibly leak sensitive data when an attacker with read access to linkahead +(i.e. the attacker needs an active user account or anonymous needs to be +enabled) can guess the name of entities or properties of referenced entities +and construct a malicious FIND or SELECT statement and when the attacker has +read permissions for an entity which references the entities containing the +sensitive information. See the bug reports for more information. + +## [0.12.0] - 2023-10-25 +(Timm Fitschen) + +### Fixed + +* `FIND ENTITY <ID> is broken`. + [linkahead-server#323](https://gitlab.indiscale.com/caosdb/src/caosdb-server/-/issues/323) +* Unknown Server Error when inserting an Entity. + [linkahead-mariadbbackend](https://gitlab.indiscale.com/caosdb/src/caosdb-mysqlbackend/-/issues/48) + +## [0.11.0] 2023-10-13 ## ### Added ### +* Configuration options `REST_RESPONSE_LOG_FORMAT` and + `GRPC_RESPONSE_LOG_FORMAT` which control the format and information included + in the log message of any response of the respective API. See + `conf/core/server.conf` for more information. +* REST API: Permanent redirect from "FileSystem" to "FileSystem/". + +### Fixed ### + +* Inheritance of the unit is not working. (GRPC API) + [linkahead-server#264](https://gitlab.indiscale.com/caosdb/src/caosdb-server/-/issues/264) +* Curly brackets in query lead to unexpected server error. + [linkahead-server#138](https://gitlab.com/linkahead/linkahead-server/-/issues/138) +* Wrong url returned by FileSystem resource behind proxy. +* `NullPointerException` in GRPC API converters when executing SELECT query on + NULL values. +* Fix parsing of decimal numbers. Fixes https://gitlab.com/linkahead/linkahead-server/-/issues/239 + +## [0.10.0] - 2023-06-02 ## +(Florian Spreckelsen) + ### Changed ### * The default behavior of the query `FIND SomeName [...]` (as well as COUNT and SELECT) is being @@ -26,12 +95,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * The InsertFilesInDir FlagJob now creates File entities without a name. The previous behavior caused severe performance problems for very large numbers of files. Issue: [#197](https://gitlab.com/caosdb/caosdb-server/-/issues/197) -### Deprecated ### - -### Removed ### - ### Fixed ### +* Unexpected Server Error when inserting an Entity. + [#216](https://gitlab.com/caosdb/caosdb-server/-/issues/216) +* Bad performance due to the execution of unnecessary jobs during retrieval. + [#189](https://gitlab.com/caosdb/caosdb-server/-/issues/189) +* Query Language: Parentheses change filter to subproperty filter + [#203](https://gitlab.com/caosdb/caosdb-server/-/issues/203) +* Searching for values in scientific notation + [#143](https://gitlab.com/caosdb/caosdb-server/-/issues/143) * Denying a role permission has no effect [#196](https://gitlab.com/caosdb/caosdb-server/-/issues/196). See security notes below. @@ -52,6 +125,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Nested queries. - Global entity permissions. +- DOC: Data model tutorial. +- Removed old documentation directory `/doc/`, migrated non-duplicate content to `/src/doc/`. ## [0.9.0] - 2023-01-19 diff --git a/CITATION.cff b/CITATION.cff index d6e80aaa203bf09b085514ff25aa5927b7965f32..77f724679e3c57ef44e9380af8ded72cb7a30410 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -23,6 +23,6 @@ authors: given-names: Stefan orcid: https://orcid.org/0000-0001-7214-8125 title: "CaosDB - Server" -version: 0.8.1 +version: 0.12.1 doi: 10.3390/data4020083 -date-released: 2022-11-07 +date-released: 2023-12-13 diff --git a/DEPENDENCIES.md b/DEPENDENCIES.md index fbcb186e45ff5bff20ca7ff20fa321f547048527..197af984701d8c522c9e419f27c32c0104501cfe 100644 --- a/DEPENDENCIES.md +++ b/DEPENDENCIES.md @@ -3,7 +3,7 @@ ## For Building and Running the Server * `>=caosdb-proto 0.3.0` -* `>=caosdb-mysqlbackend 5.0.0` +* `>=caosdb-mysqlbackend 7.0.0` * `>=Java 11` * `>=Apache Maven 3.6.0` * `>=Make 4.2` @@ -13,7 +13,7 @@ ## For Deploying a Web User Interface (optional) -* `>=caosdb-webui 0.8.0` +* `>=caosdb-webui 0.13.0` ## For Building the Documentation (optional) diff --git a/README_SETUP.md b/README_SETUP.md index acb3a79ad256fbba6a0d0a69887bb7c7ba7b28b9..567a24663d5c1c7a3343e3e0f137b8196831c7bd 100644 --- a/README_SETUP.md +++ b/README_SETUP.md @@ -163,6 +163,9 @@ sources (if you called `make run` previously). `$ make test` +You can run single unit test with +`mvn test -X -Dtest=TestCQL#testDecimalNumber` + ## Setup Eclipse @@ -238,6 +241,7 @@ Stand-alone documentation is built using Sphinx: `make doc` - recommonmark - sphinx - sphinx-rtd-theme +- sphinx-a4doc - sphinxcontrib-plantuml - javasphinx :: `pip3 install --user javasphinx` - Alternative, if javasphinx fails because python3-sphinx is too recent: diff --git a/RELEASE_GUIDELINES.md b/RELEASE_GUIDELINES.md index 1b5ae48906d4e8328064ecaf7e7c4f7a4a2bd20f..f6f3c8729d4dc5ef84bd53be630d7a82bf2dfd65 100644 --- a/RELEASE_GUIDELINES.md +++ b/RELEASE_GUIDELINES.md @@ -26,23 +26,25 @@ guidelines of the CaosDB Project 5. Merge the release branch into the main branch. -6. Tag the latest commit of the main branch with `v<VERSION>`. +6. Wait for the main branch pipelines to pass. -7. Delete the release branch. +7. Tag the latest commit of the main branch with `v<VERSION>`. -8. Merge the main branch back into the dev branch. +8. Delete the release branch. -9. Update the versions for the next developement round: +9. Merge the main branch back into the dev branch. + +10. Update the versions for the next developement round: * [pom.xml](./pom.xml) with a `-SNAPSHOT` suffix * `src/doc/conf.py` * `CHANGELOG.md`: Re-add the `[Unreleased]` section. -10. Add a gitlab release in the respective repository: - https://gitlab.indiscale.com/caosdb/src/caosdb-server/-/releases +11. Add a gitlab release in the respective repository: + https://gitlab.com/linkahead/linkahead-server/-/releases Add a description, which can be a copy&paste from the CHANGELOG, possibly prepended by: ```md # Changelog -[See full changelog](https://gitlab.indiscale.com/caosdb/src/caosdb-server/-/blob/${TAG}/CHANGELOG.md) +[See full changelog](https://gitlab.com/linkahead/linkahead-server/-/blob/${TAG}/CHANGELOG.md) ``` diff --git a/caosdb-webui b/caosdb-webui index 421b2dce5199a5a2c96bcc638543c1fd51d48870..6e4db2f99e1d441bbda9ccca85fae45526018406 160000 --- a/caosdb-webui +++ b/caosdb-webui @@ -1 +1 @@ -Subproject commit 421b2dce5199a5a2c96bcc638543c1fd51d48870 +Subproject commit 6e4db2f99e1d441bbda9ccca85fae45526018406 diff --git a/conf/core/jobs.csv b/conf/core/jobs.csv index e2fe974b4304e594e58ce62599b36d389885ddf7..6f6be8723aabc7205d9cacbfdf0b8bf2b65c8bf7 100644 --- a/conf/core/jobs.csv +++ b/conf/core/jobs.csv @@ -1,8 +1,8 @@ # # This file is a part of the CaosDB Project. # -# Copyright (C) 2021 Timm Fitsche <t.fitschen@indiscale.com> -# Copyright (C) 2021 IndiScale GmbH <info@indiscale.com> +# Copyright (C) 2021-2023 Timm Fitsche <t.fitschen@indiscale.com> +# Copyright (C) 2021-2023 IndiScale GmbH <info@indiscale.com> # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as @@ -22,6 +22,7 @@ # DOMAIN_ID, ENTITY_ID, TRANSACTION, JOB, JOB_FAILURE_SEVERITY # general rules +0,0,INSERT,GenerateEntityId,ERROR 0,0,INSERT,CheckPropValid,ERROR 0,0,INSERT,CheckParValid,ERROR 0,0,INSERT,CheckParOblPropPresent,ERROR @@ -30,8 +31,7 @@ 0,0,UPDATE,CheckParValid,ERROR 0,0,UPDATE,CheckParOblPropPresent,ERROR 0,0,UPDATE,CheckValueParsable,ERROR -0,0,DELETE,CheckReferenceDependencyExistent,ERROR -0,0,DELETE,CheckChildDependencyExistent,ERROR +0,0,DELETE,CheckDependenciesBeforeDeletion,ERROR # role specific rules diff --git a/conf/core/log4j2-default.properties b/conf/core/log4j2-default.properties index 5983f8e33d51729c70cd3d685fe299aa13e8f27c..39f5f0ead11bb5ccfa9e39d3007d40ee8fa3077c 100644 --- a/conf/core/log4j2-default.properties +++ b/conf/core/log4j2-default.properties @@ -8,6 +8,10 @@ verbose = true property.LOG_DIR = log property.REQUEST_TIME_LOGGER_LEVEL = OFF +# the root logger has level "WARN" but we want to log the GRPC traffic in INFO level: +logger.grpc_response_log.name = org.caosdb.server.grpc.LoggingInterceptor +logger.grpc_response_log.level = INFO + ## appenders # stderr appender.stderr.type = Console diff --git a/conf/core/server.conf b/conf/core/server.conf index 864460395dfe92c3078aab8c331be1c4e604916a..626a9c541103708ff54dcfcc817df5a861115cb2 100644 --- a/conf/core/server.conf +++ b/conf/core/server.conf @@ -66,7 +66,7 @@ MYSQL_USER_NAME=caosdb # Password for the user MYSQL_USER_PASSWORD=random1234 # Schema of mysql procedures and tables which is required by this CaosDB instance -MYSQL_SCHEMA_VERSION=v6.0-SNAPSHOT +MYSQL_SCHEMA_VERSION=v8.0-SNAPSHOT # -------------------------------------------------- @@ -97,6 +97,21 @@ GRPC_SERVER_PORT_HTTPS=8443 # HTTP port of the grpc end-point GRPC_SERVER_PORT_HTTP= +# -------------------------------------------------- +# Response Log formatting (this cannot be configured by the logging frame work +# and thus has to be configured here). +# -------------------------------------------------- + +# Logging format of the GRPC API. +# Known keys: user-agent, local-address, remote-address, method. +# 'OFF' turns off the logging. +GRPC_RESPONSE_LOG_FORMAT={method} {local-address} {remote-address} {user-agent} +# Logging format of the REST API. +# Known keys: see column "Variable name" at https://javadocs.restlet.talend.com/2.4/jse/api/index.html?org/restlet/util/Resolver.html +# 'OFF' turns off the logging. +# Leaving this empty means using restlet's default settings. +REST_RESPONSE_LOG_FORMAT= + # -------------------------------------------------- # HTTPS options # -------------------------------------------------- @@ -222,4 +237,4 @@ ENTITY_VERSIONING_ENABLED=true # Enabling the state machine extension -# EXT_STATE_ENTITY=ENABLE \ No newline at end of file +# EXT_STATE_ENTITY=ENABLE diff --git a/conf/core/usersources.ini.template b/conf/core/usersources.ini.template index fb3285723f0721da3280e5ef33461ff4b684e89f..d54d91d2202bf3ba465346ca0a24ad566bbd73a8 100644 --- a/conf/core/usersources.ini.template +++ b/conf/core/usersources.ini.template @@ -46,11 +46,14 @@ class = org.caosdb.server.accessControl.Pam # scripts or the misc/pam_authentication/ldap_authentication.sh script here. ; pam_script = ./misc/pam_authentication/pam_authentication.sh default_status = ACTIVE -# Only users which fulfill these criteria are accepted. +# Only users which fulfill these criteria are accepted. The values are +# user/group name(s) separated by whitespaces ;include.user = [uncomment and put your users here] ;include.group = [uncomment and put your groups here] ;exclude.user = [uncomment and put excluded users here] ;exclude.group = [uncomment and put excluded groups here] # It is typically necessary to add at least one admin -;user.[uncomment a set a username here].roles = administration +;user.[uncomment and set a username here].roles = administration +# Several roles are separated by commas +;user.[uncomment and set a username here].roles = role1, role2, role with spaces diff --git a/doc/Authentication.md b/doc/Authentication.md deleted file mode 100644 index a7e424b4c321156a009d8a7d9631f32dd296ce1a..0000000000000000000000000000000000000000 --- a/doc/Authentication.md +++ /dev/null @@ -1,81 +0,0 @@ - - -Author: Timm Fitschen - -Email: timm.fitschen@ds.mpg.de - -Date: Older than 2016 - - Some features of CaosDB are available to registered users only. Making any changes to the data stock via HTTP requires authentication by `username` _plus_ `password`. They are to be send as a HTTP header, while the password is to be hashed by the sha512 algorithm: - -| `username:` | `$username` | -|-------------|-------------|- -| `password:` | `$SHA512ed_password` | - - -# Sessions - -## Login - -### Request Challenge - - * `GET http://host:port/login?username=$username` - * `GET http://host:port/login` with `username` header - -*no password required to be sent over http* - -The request returns an AuthToken with a login challenge as a cookie. The AuthToken is a dictionary of the following form: - - - {scope=$scope; - mode=LOGIN; - offerer=$offerer; - auth=$auth - expires=$expires; - date=$date; - hash=$hash; - session=$session; - } - - $scope:: A uri pattern string. Example: ` {**/*} ` - $mode:: `ONETIME`, `SESSION`, or `LOGIN` - $offerer:: A valid username - $auth:: A valid username - $expires:: A `YYYY-MM-DD HH:mm:ss[.nnnn]` date string - $date:: A `YYYY-MM-DD HH:mm:ss[.nnnn]` date string - $hash:: A string - $session:: A string - -The challenge is solved by concatenating the `$hash` string and the user's `$password` string and calculating the sha512 hash of both. Pseudo code: - - - $solution = sha512($hash + sha512($password)) - -### Send Solution - -The old $hash string in the cookie has to be replaces by $solution and the cookie is to be send with the next request: - -`PUT http://host:port/login` - -The server will return the user's entity in the HTTP body, e.g. - - - <Response ...> - <User name="$username" ...> - ... - </User> - </Response> - -and a new AuthToken with `$mode=SESSION` and a new expiration date and so on. This AuthToken cookie is to be send with every request. - -### Logout - -Send - -`PUT http://host:port/logout` - -with a valid AuthToken cookie. No new AuthToken will be returned and no AuthToken with that `$session` will be accepted anymore. - - - - diff --git a/doc/Datatype.md b/doc/Datatype.md deleted file mode 100644 index 246f52e103eeb57e1bee355788260508a3df8619..0000000000000000000000000000000000000000 --- a/doc/Datatype.md +++ /dev/null @@ -1,95 +0,0 @@ - - -# TEXT -* Description: TEXT stores stores any text values. -* Range: Any [utf-8](https://en.wikipedia.org/wiki/UTF-8) encodable sequence of characters with maximal 65,535 bytes. (Simply put: In most cases, any text with less than 65,535 letters and spaces will work. But if you use special characters like `à `, `€` or non-latin letters then the number of bytes, which are needed to store it, increases. Then the effective maximal length is smaller than 65,535. A bad case scenario would be a text in Chinese. Chinese characters need about three times the space of letters from the latin alphabet. Therefore, only 21845 Chinese characters can be stored within this datatype. Which is still quite a lot I guess :D) -* Examples: - * `Am Faßberg 17, D-37077 Göttingen, Germany` - * `Experiment went well until the problem with the voltmeter occured. Don't use the results after that.` - * `someone@email.org` - * `Abstract: bla bla bla ...` - * `Head of Group` - * `http://www.bmp.ds.mpg.de` - * - - A. Schlemmer, S. Berg, TK Shajahan, S. Luther, U. Parlitz, - Quantifying Spatiotemporal Complexity of Cardiac Dynamics using Ordinal Patterns, - 37th Annual International Conference of the IEEE Engineering in Medicine and Biology Society (EMBC), 2015, doi: 10.1109/EMBC.2015.7319283 - ----- - -# BOOLEAN -* Description: BOOLEAN stores boolean `TRUE` or `FALSE`. It is therefore suitable for any variable that represents that something is the case or not. -* Accepted Values: `TRUE` or `FALSE`, case insensitive (i.e. it doesn't matter if you use capitals or small letters). -* Note: You could also use a TEXT datatype to represent booleans (or even INTEGER or DOUBLE). But it makes a lot of sense to use this special datatype as it ensures that only the two possible values, `TRUE` or `FALSE` are inserted into the database. Every other input would be rejected. This helps to keep the database understandable and to avoid mistakes. - ----- - -# INTEGER -* Description: INTEGER stores integer numbers. If you need floating point variables, take a look at DOUBLE. -* Range: `-2147483648` to `2147483647`, `-0` is interpreted and stored as `0`. -* Note: This rather limited range is just provisional. It can be extended with low effort as soon as requested. - ----- - -# DOUBLE -* Description: DOUBLE stores floating point numbers with a double precision as defined by [IEEE 754](https://en.wikipedia.org/wiki/IEEE_floating_point). -* Range: - * From `2.2250738585072014E-308` to `1.7976931348623157E308` (negative and positive) with a precision of 15 decimals. - * Any other decimal number _might work_ but it is not guaranteed. - * `-0`, `0`, `NaN`, `-inf` and `inf` -* Note: The server generates a warning when the precision of the submitted DOUBLE value is to high to be preserved. - ----- - -# DATETIME -The DateTime data type exists in (currently) three flavors which are dynamically chosen during parsing on the the serverside. The flavors have different ranges, support of time zones and intended use cases. Only the first two flavors are actually implemented for storage and queries. The third one is implemented for queries exclusively. - -## UTCDateTime -* Description: This DATETIME flavor stores values which represent a single point of time according to [UTC](https://en.wikipedia.org/wiki/Coordinated_Universal_Time) with the format specified by [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) (Combined date and time). It does support [UTC Leap Seconds](https://en.wikipedia.org/wiki/Leap_second) and time zones. -* Range: From `-9999-01-01T00:00:00.0UTC` to `9999-12-31T23:59:59.999999999UTC` with nanosecond precision. -* Examples: - * `2016-01-01T13:23:00.0CEST` which means _January 1, 2016, 1:23 PM, Central European Summer Time_. - * `-800-01-01T13:23:00.0` which means _January 1, 800 BC, 1:23 PM, UTC_. -* Note: - * It is allowed to ommit the nanosecond part of a UTCDateTime (`2016-01-01T13:23:00CEST`). This indicates a precision of seconds for a UTCDateTime value. - -## Date - Description:: This DATETIME flavor stores values which represent a single date, month or year according to the [gregorian calendar](https://en.wikipedia.org/wiki/Gregorian_Calendar). A month/year is conceived as a single date with the presion of a month/year. This concept is useful if you try to understand the query semantics which are explained [elsewhere](./QueryLanguage#POVDateTime). - Format:: `Y[YYY][-MM[-dd]]` (where square brackets mean that the expression is optional). - Range:: Any valid date according to the gregorian calendar from `-9999-01-01` to `9999-12-31` (and respective dates with lower precision. E.g. the year `-9999`). There is no year `0`. -* Note: Date is a specialization of [#SemiCompleteDateTime]. - -## SemiCompleteDateTime -* Description: A generalization of the _Date_ and _UTCDateTime_ flavors. In general, there is no time zone support. Although this flavor is not yet storable in general, it is implemented for search queries yet. I.e. you might search for `FIND ... date>2015-04-03T20:15` yet. -* Format: `Y[YYY]['-MM[-dd[Thh:[mm[:ss[.ns]]]]]]]`. -* Special Properties: For every SemiCompleteDateTime _d_ there exists a _Inclusive Lower Bound_ (`d.ILB`) and a _Exclusive Upper Bound_ (`d.EUB`). That means, a SemiCompleteDateTime can be interpreted as an interval of time. E.g. `2015-01` is the half-open interval `[2015-01-01T00:00:00.0, 2016-01-01T00:00:00.0)`. ILB and EUB are UTCDateTimes respectively. These properties are important for the semantics of the the query language, especialy the [operators](./QueryLanguage#POVDateTime). - -## Future Flavors -Please file a new feature request as soon as you need them. -* Time:: For a time of the day (without the date). Supports time zones. -* FragmentaryDateTime:: For any fragmentary DateTime. That is an arbitrary combination of year, month, day of week, day of month, day of year, hour of day, minute, seconds (and nanoseconds). This flavor is useful for recurrent events like a bus schedule (_Saturday, 7:30_) or the time of a standing order for money transfer (_third day of the month_). - ----- - -# REFERENCE -* Description: REFERENCE values store the [Valid ID](./Glossary#valid-id) of an existing entity. The are useful to establish links between two entities. -* Accepted Values: Any [Valid ID](./Glossary#valid-id) or [Valid Unique Existing Name](./Glossary#valid-unique-existing-name) or [Valid Unique Temporary ID](./Glossary#valid-unique-temporary-id) or [Valid Unique Prospective Name](./Glossary#valid-unique-prospective-pame). -* Note: - * After beeing processed successfully by the server the REFERENCE value is normalized to a [Valid ID](./Glossary#valid-id). I.e. it is guaranteed that a REFERENCE value of a valid property is a positive integer. - -## FILE -* Description: A FILE is a special REFERENCE. It only allows entity IDS which belong to a File. - -## RecordType as a data type -* Furthermore, any RecordType can be used as a data type. This is a variant of the REFERENCE data type where any entity is a valid value which is a child of the RecordType in question. -* Example: - * Let `Person` be a RecordType, `Bertrand Russel` be a child of `Person`. Then `Bertrand Russel` is a valid value for a property with a `Person` data type. - -# LIST -* Description: A LIST is always a list of something which has another data type. E.g. A LIST of TEXT values, a LIST of REFERENCES value, etc. Here we call TEXT resp. REFERENCE the **Element Data Type**. The LIST data type allows you to store an arbitrary empty or non-empty ordered set (with duplicates) of values of the *same* data type into one property. Each value must be a valid value of the Element Data Type. -* Example: - * LIST of INTEGER: ```[0, 2, 4, 5, 8, 2, 3, 6, 7]``` - * LIST of Person, while `Person` is a RecordType: ```['Bertrand Russel', 'Mahatma Ghandi', 'Mother Therese']``` - - diff --git a/doc/Entity.md b/doc/Entity.md deleted file mode 100644 index c2ff4acc733638f964f6d6343ef4dfd4db05ebc1..0000000000000000000000000000000000000000 --- a/doc/Entity.md +++ /dev/null @@ -1,190 +0,0 @@ -Version: 0.1.0r1 - -Author: Timm Fitschen - -Email: timm.fitschen@ds.mpg.de - -Date: 2017-12-17 - -# Introduction - -CaosDB is a database management system that stores it's data into `Entities`. An `Entity` can be thought of as the equivalent to tables, rows, columns and the tuples that fill the tables of a traditional RDBMS. Entities are not only used to store the data they also define the structure of the data. - -# Formal Definition - -An `Entity` may have - -* a `domain` -* an `id` -* a `role` -* a `name` -* a `data type` -* a `Set of Values` -* a `Set of Properties` -* a `Set of Parents` - -A `domain` contains an `Entity`. - -An `id` is an arbitrary string. - -A `role` is an arbitrary string. Especially, it may be one of the following strings: - -* `RecordType` -* `Record` -* `Relation` -* `Property` -* `File` -* `QueryTemplate` -* `Domain` -* `Unit` -* `Rule` -* `DataType` -* `Remote` - -A `name` is an arbitrary string. - -A `data type` contains an `Entity`. Note: this is not necessarily a `Data Type`. - -## Set of Values - -A `Set of Values` is a mapping from a `indices` to a finite set of `Values`. - -An `index` is an interval of non-negative integers starting with zero. - -### Value - -A `Value` may have a `data type` and/or a `unit`. - -A `data type` is an `Entity`. Note: this is not necessarily a `Data Type`. - -A `unit` is an arbitrary string. - -## Data Type - -A `Data Type` is an `Entity` with role `DataType`. - -### Reference Data Type - -A `Reference Data Type` is a `Data Type`. It may have a `scope`. - -A `scope` contains an `Entity`. - -### Collection Data Type - -A `Collection Data Type` is a `Data Type`. It may have an ordered set of `elements`. - -## Record Type - -A `Record Type` is an `Entity` with role `RecordType`. - -## Record - -A `Record` is an `Entity` with role `Record`. - -## Relation - -A `Relation` is an `Entity` with role `Relation`. - -## Property - -A `Property` is an `Entity` with role `Property`. It is also refered to as `Abstract Property`. - -## File - -A `File` is an `Entity` with role `File`. - -A `File` may have - -* a `path` -* a `size` -* a `checksum` - -A `path` is an arbitrary string. - -A `size` is a non-negative integer. - -A `checksum` is an ordered pair (`method`,`result`). - -A `method` is an arbitrary string. - -A `result` is an arbitrary string. - -## QueryTemplate - -A `QueryTemplate` is an `Entity` with role `QueryTemplate`. - -## Domain - -A `Domain` is an `Entity` with role `Domain`. - -## Unit - -A `Unit` is an `Entity` with role `Unit`. - -## Rule - -A `Rule` is an `Entity` with role `Rule`. - -## Remote - -A `Remote` is an `Entity` with role `Remote`. - -## Set of Parents - -A `Set of Parents` is a set of `Parents`. - -### Parent - -A `Parent` may contain another `Entity`. - -A `Parent` may have an `affiliation`. - -An `affiliation` may contain of the following strings: - -* `subtyping` -* `instantiation` -* `membership` -* `parthood` -* `realization` - -## Set of Properties - -A `Set of Properties` is a tripple (`index`, set of `Implemented Properties`, `Phrases`). - -An `index` is a bijective mapping from an interval of non-negative integer numbers starting with zero to the set of `Implemented Properties`. - -### Implemented Property - -An `Implemented Property` contains another `Entity`. - -An `Implemented Property` may have an `importance`. - -An `Implemented Property` may have a `maximum cardinality`. - -An `Implemented Property` may have a `minimum cardinality`. - -An `Implemented Property` may have an `import`. - -An `importance` is an arbitrary string. It may contain of the following strings: - -* `obligatory` -* `recommended` -* `suggested` -* `fix` - -A `maximum cardinality` is a non-negative integer. - -A `minimum cardinality` is a non-negative integer. - -An `import` is an arbitrary string. It may contain of the following strings: - -* `fix` -* `none` - -### Phrases - -`Phrases` are a mapping from the cartesian product of the `index` with itself to a `predicate`. - -A `predicate` is an arbitrary string. - - diff --git a/doc/FileServer.md b/doc/FileServer.md deleted file mode 100644 index 64be8ef910d76ba6c1a304ddbdd64e4e25d31d56..0000000000000000000000000000000000000000 --- a/doc/FileServer.md +++ /dev/null @@ -1,132 +0,0 @@ - -Author: Timm Fitschen - -Email: timm.fitschen@ds.mpg.de - -Date: 2014-06-17 - - -# Info -There are several ways to utilize the file server component of CaosDB. It is possible to upload a file or a whole folder including subfolders via HTTP and the _drop off box_. It is possible to download a file via HTTP identified by its ID or by its path in the internal file system. Furthermore, it is possible to get the files metadata via HTTP as an xml. - -# File upload -## Drop off box - -The drop off box is a directory on the CaosDB server's local file system, specified in the `server.conf` file in the server's basepath (something like `~/CaosDB/server/server.conf`). The key in the `server.conf` is called `dropoffbox`. Since the drop off box directory is writable for all, users can push their files or complete folders via a `mv` or a `cp` (recommended!) in that folder. The server deletes files older than their maximum lifetime (24 hours by default, specified `in server.conf`). But within their lifetime a user can prompt the server to pick up the file (or folder) from the drop off box in order to transfer it to the internal file system. - -Now, the user may send a pick up request to `POST http://host:port/FilesDropOff` with a similar body: - - <Post> - <File pickup="$path_dropoffbox" destination="$path_filesystem" description="$description" generator="$generator"/> - ... - </Post> - -whereby -* $path_dropoffbox is the actual relative path of the dropped file or folder in the DropOffBox, -* $path_filesystem is the designated relative path of that object in the internal file system, -* $description is a description of the file to be uploaded, -* $generator is the tool or client used for pushing this file. - -After a successful pick up the server will return: - - <Response> - <File description="$description" path="$path" id="$id" checksum="$checksum" size="$size" /> - ... - </Response> - -whereby -* $id is the new generated id of that file and -* $path is the path of the submitted file or folder relative to the file system's root. - -## HTTP upload stream -### Files - -File upload via HTTP is implemented in a [rfc1867](http://www.ietf.org/rfc/rfc1867.txt) consistent way. This is a de-facto standard that defines a file upload as a part of an HTML form submission. This concept shall not be amplified here. But it has to be noticed that this protocol is not designed for uploads of complete structured folders. Therefore the CaosDB file components have to impose that structure on the upload protocol. - -CaosDB's file upload resource does exclusively accept POST requests of MIME media type `multipart/form-data`. The first part of each POST body is expected to be a form-data text field, containing information about the files to be uploaded. It has to meet the following requirements: -* `Content-type: text/plain; charset=UTF-8` -* `Content-disposition: form-data; name="FileRepresentation"` - -If the content type of the first part is not `text/plain; charset=UTF-8` the server will return error 418. If the body is not actually encoded in UTF-8 the servers behaviour is not defined. If the field name of the first part is not `FileRepresentation` the server will return error 419. - -The body of that first part is to be an xml document of the following form: - - - <Post> - <File upload="$temporary_identifier" destination="$path_filesystem" description="$description" checksum="$checksum" size="$size"/> - ... - </Post> - -whereby -* $temporary_identifier is simply a arbitrary name, which will be used to identify this `<File>` tag with a uploaded file in the other form-data parts. -* $path_filesystem is the designated relative path of that object in the internal file system, -* $description is a description of the file to be uploaded, -* $size is the files size in bytes, -* $checksum is a SHA-512 Hash of the file. - -The other parts (which must be at least one) may have any appropriate media type. `application/octet-stream` is a good choice for it is the default for any upload file according to [rfc1867](http://www.ietf.org/rfc/rfc1867.txt). Their field name may be any name meeting the requirements of [rfc1867](http://www.ietf.org/rfc/rfc1867.txt) (most notably they must be unique within this POST). But in order to identify the corresponding xml file representation of each file the `filename` parameter of the content-disposition header has to be set to the proper $temporary_identifier. The Content-disposition type must be `form-data`: -* `Content-disposition: form-data; name="$any_name"; filename="$temporary_identifier"` - -Finally the body of these parts have to contain the file encoded in the proper `Content-Transfer-Encoding`. - - -If a file part has a `filename` parameter which doesn't occur in the xml file representation the server will return error 420. The file will not be stored anywhere. If an xml file representation has no corresponding file to be uploaded (i.e. there is no part with the same `filename`) the server will return error 421. Some other error might occur if the checksum, the size, the destination etc. are somehow corrupted. - -### Folders - -Uploading folders works in a similar way. The first part of the `multipart/form-data` document is to be the representation of the folders: - - - <Post> - <File upload="$temporary_identifier" destination="$path_filesystem" description="$description" checksum="$checksum" size="$size"/> - ... - </Post> - -The root folder is represented by a part which has a header of the form: -* `Content-disposition: form-data; name="$any_name"; filename="$temporary_identifier/"` -The slash at the end of the `filename` indicates that this is a folder, not a file. Consequently, the body of this part will be ignored and should be empty. -Any file with the name `$filename` in the root folder is represented by a part which has a header of the form: -* `Content-disposition: form-data; name="$any_name"; filename="$temporary_identifier/$filename"` -Any sub folder with the name `$subfolder` is represented by a part which has a header of the form: -* `Content-disposition: form-data; name="$any_name"; filename="$temporary_identifier/$subfolder/"` - -Likewise, a complete directory tree can be transfered by appending the structure to the `filename` header field. - -**Example**: -Given the structure - - rootfolder/ - rootfolder/file1 - rootfolder/subfolder/ - rootfolder/subfolder/file2 - -an upload document would have the following form: - - ... (HTTP Header) - Content-type: multipart/form-data, boundary=AaB03x - - --AaB03x - content-disposition: form-data; name="FileRepresentation" - - <Post> - <File upload="tmp1234" destination="$path_filesystem" description="$description" checksum="$checksum" size="$size"/> - </Post> - - --AaB03x - content-disposition: form-data; name="random_name1"; filename="temp1234/" - - --AaB03x - content-disposition: form-data; name="random_name1"; filename="temp1234/file1" - - Hello, world! This is file1. - - --AaB03x - content-disposition: form-data; name="random_name1"; filename="temp1234/subfolder/" - - --AaB03x - content-disposition: form-data; name="random_name1"; filename="temp1234/subfolder/file2" - - Hello, world! This is file2. - - --AaB03x-- - diff --git a/doc/Message.md b/doc/Message.md deleted file mode 100644 index 5c5198d80974a6114d32c37224b22ff8ffb47b47..0000000000000000000000000000000000000000 --- a/doc/Message.md +++ /dev/null @@ -1,166 +0,0 @@ -# Introduction - -API Version 0.1.0 - -A Message is a way of communication between the server and a client. The main purpose is to inform the clients about errors which occured during transactions, issue warnings when entities have a certain state or just explicitly confirm that a transaction was successful. Messages represents information that is not persistent or just the reproducible outcome of a transaction. Messages are not stored aside from logging. - -# Message Classes And Their Properties - -## Message (generic super class) - -A `Message` must be either a `Server Message` or a `Client Message`. - -A `Message` must have a `description`. A `description` is a string and a human-readable explanation of the meaning and/or purpose of the message. The description must not have leading or trailing whitespaces. The description should be kept in English. For the time being there is no mechanism to indicate that the description is written in other languages. This could be changed in later versions of this API. - -## Server Message - -A `Server Message` is a Message issued by the server. It must not be issued by clients. - -A `Server Message` may be either a `Standard Server Message` or a `Non-Standard Server Message` - -### Standard Server Message - -A `Standard Server Message` is one of a set of predefined messages with a certain meaning. The set of these `Standard Server Messages` is maintained and documented in the Java code of the server. There should be a server resource for these definitions in order to have a always up-to-date documentation of the messages on every server. - -A `Standard Server Message` must have an `id`. An `id` is a non-empty string that uniquely identifies a standard server message. An id should consist only of ASCII compliant upper-case Latin alphabetic letters from `A` to `Z` and the underscore character `_`. -An `id` of a `Standard Server Message` must not start with the string `NSSM_`. - -A `Standard Server Message` must have a `type`. A `type` is one these strings: `Info`, `Warning`, `Error`, or `Success`. - -#### Error Message - -A `Server Message` with type `Error` is also called `Error Message` and sometimes just `Error`. An `Error Message` indicates that a request has *failed*. It informs about the reasons for that failure or the nature of the problems which occurred. The description of each error message should explain the error and indicate if and how the client can remedy the problems with her request. - -#### Warning Message - -A `Server Message` with type `Error` is also called `Warning Message` and sometime just `Warning`. A `Warning Message` indicates that certain *irregularities* occurred during the processing of the request or that the client requested something that is *not recommended but not strictly forbidden*. - -#### Info Message - -A `Server Message` with type `Info` is also called `Info Message` and sometimes just `Info`. An `Info Message` is a means to inform the client about *arbitrary events* which occurred during the processing of the request and which are *not* to be considered *erroneous* or *non-recommended*. These info messages are primarily intended to make the processing of the request more understandable for the client. Info messages are not meant to be used for debugging. - -#### Success Message - -A `Server Message` with type `Success` is also called a `Success Message`. A `Success Message` indicates the successful *state change* due to portions of a request or the whole request. A success message must not be issued if the request fails. - -### Non-Standard Server Message - -A `Non-Standard Server Message` may be issued by any non-standard server plugin or extension. It is a placeholder for extensions to the Message API. - -A `Non-Standard Server Message` may have an `id`. An `id` is a non-empty string. It should consist only of ASCII compliant upper-case Latin alphabetic letters from `A` to `Z` and the underscore character `_`. However, the id should not be equal to any id from the set of predefined standard server messages. Furthermore, the id of a non-standard server message should start with the string `NSSM_`. - -A `Non-Standard Server Message` may have a `type`. A `type` is a non-empty string. It should consist only of ASCII compliant upper-case or lower-case Latin alphabetic letters from `a` to `z`, from `A` to `Z`, and the underscore character `_`. If the type is equal to one of the above-mentioned types, it must have the same meaning and the same effects on the request as the respective type from above. Especially, a message with type `Error` must not be issued unless the request actually fails. Likewise a `Success` must not be issued unless the request actually caused a *state change* of the server. - -## Client Message - -A `Client Message` may have an `ignore` flag. The `ignore` flag can have one of these values: `no`, `yes`, `warn`, `silent` - -A `Client Message` is a message issued by a client. It should not be issued by the server. A `Client Message` may be completely ignored by clients. A client message must not be ignored by the server. A `Client Message` which cannot be understood by the server must result in an error, unless the `ignore` flag states otherwise. - -### Ignore Flag - -If the `ignore` flag is set to `no` the server must not ignore the client message. If the server cannot understand the client message an error must be issued. This will cause the transaction to fail. - -If the `ignore` flag is set to `yes` the server must ignore the client message. - -If the `ignore` flag is set to `warn` the server should not ignore the message. If the server cannot understand the client message, a warning must be issued. The transaction will not fail due to this warning. - -## Message Parameters - -A `Message` may have zero or more parameters. A `Message Parameter` is a a triple of a `key`, a `value`. It is intended to facilitate the processing and representation of messages by clients and the server. For example, consider an `Error Message` which states that a certain server state cannot be reached and the reason be that there is an entity with certain features. Then it is useful to refer to the entity via the -parameters. A client can now resolve the entity and just show it or generate a URI for this entity. - -A `key` is a non-empty string which should consist only of ASCII compliant lower-case Latin alphabetic letters from `a` to `z` and the minus character `-`. A `key` must be unique among the keys of the message parameters. - -A `value` is a possibly empty, arbitrary string which must not have leading or trailing white spaces. - -A `Message Parameter` may have a `type`. The `type` of a `Message Parameter` is also called a `Message Parameter Type`. A `Message Parameter Type` is a non-empty string which should consist only of ASCII compliant lower-case Latin alphabetic letters from `a` to `z` and the minus character `-`. A message parameter type may be one these string: `entity-id`, `entity-name`, `entity-cuid`, `property-index`, `parent-id`, `parent-name`. - -A `Message Parameter` with a type which begins with `entity-` is also called an `Entity Message Parameter`. The value of an `Entity Message Parameter` must refer to an entity—via its id, name, or cuid, respectively. - -A `Message Parameter` with a type which begins with `property-` is also called a `Property Message Parameter`. The value of such a parameter must refer to an entity's property. In the case of the `property-index` type the value refers to a property via a zero-based index (among the list of properties of that entity). The list of properties in question must belong to the `Message Bearer` which must in turn be an `Entity`. - -A `Message Parameter` with a type which begins with `parent-` is also called a `Parent Message Parameter`. The value of such a parameter must refer to an entity's parent via its id or name, respectively. - -## Message Bearer - -A `Message` must have a single `Message Bearer`, or, equivalently, a `Message` `belongs to` a single `Message Bearer`. The message is usually considered to carry information about the message bearer if not stated otherwise. The message's subject should be the message bearer itself, so to speak. Although, possibly indicated by a `Message Parameter` the message may be additionally or solely concerned with other things than the message bearer. Please note: The message bearer may also indicate the context of evaluation of the message parameters, e.g. when the type of the message parameter is `property-index`. - -A `Message Bearer` may be an `Entity`, a `Property`, a `Container`, a `Request`, a `Response`, or a `Transaction`. - -# Representation and Serialization - -Messages can be serialized, deserialized by the means of XML. - -## XML Representation - -A `Message` is serialized into a single XML Element Node (hereafter the *root element* with zero or more Child Nodes. - -#### Root Element Tag - -The root element's tag of a `Server Message` must be equal to its `type` if and only if the type is equal to one of the allowed types of a `Standard Server Message` (even if it is a Non-Standard Server Message). Otherwise the root tag is just 'ServerMessage'. - -```xml -<Error/><!--an Error Message--> -<Warning/><!--a Warning Message--> -<Info/><!--an Info Message--> -<Success/><!--a Success Message--> -<ServerMessage/> <!--a Non-Standard Server Message with a non-standard type--> -``` - -The root element's tag of a `Client Message` must be 'ClientMessage'. E.g. -```xml -<ClientMessage/><!--a Client Message--> -``` - -#### Root Element Attributes - -The root element must have the attributes nodes `id`, and/or `ignore` if and only if the messages have corresponding properties. The root element must have a 'type' attribute only if the message has a type property and if the type is not equal to the root element's tag. The values of the attributes must equal the corresponding properties. E.g. - -```xml -<Error id="ENTITY_DOES_NOT_EXIST" type="Error"/><!--this and the next element are equivalent--> -<Error id="ENTITY_DOES_NOT_EXIST"/> -<ServerMessage type="CustomType"/><!--has no id--> -<ServerMessage id="NSSM_MY_ID"/><!--has no type--> -``` - -or - -```xml -<ClientMessage id="CM_MY_ID" ignore="warn"/> -``` - -All other Attributes should be ignored. - -#### Description Element - -The root element must have exactly one Child Element Node with tag 'Description' if and only if the message has a `description` property. The string value of the message's description must be the first Child Text Node of the 'Description' Element. E.g. - -```xml -<ServerMessage> - <Description>This is a description.</Description> -</ServerMessage> -``` - -Please note: Any leading or trailing whitespaces of the Text Node must be stripped during the deserialization. - -All other Attributes and Child Nodes should be ignored. - -#### Parameters Element - -The root element must have exactly one Child Element Node with tag 'Parameters' if the message has at least one `parameter`. The 'Parameters' Element in turn must have a single Child Element Node for each parameter which are called `Parameter Elements`. - -A `Parameter Element` must have a tag equal to the `key` of the parameter. -It must have a `type` attribute equal to the `type` property of the parameter if and only if the parameter has a type. And it must have a first Child Text Node which is equal to the parameter's `value`. E.g. - -```xml -<ClientMessage> - <Parameters> - <param-one type="entity-name">Experiment</param-one><!--One parameter with key="param-one", value="Experiment", and type="entity-name"--> - </Parameters> -</ClientMessage> -``` - -Please note: Any leading or trailing whitespaces of the Text Node must be stripped during the deserialization. - -All other Attributes and Child Nodes below the 'Parameters' Element should be ignored. diff --git a/doc/Paging.md b/doc/Paging.md deleted file mode 100644 index 608ebc0489d35e5e60ff9cb0c5c28846d729a1ca..0000000000000000000000000000000000000000 --- a/doc/Paging.md +++ /dev/null @@ -1,24 +0,0 @@ -The Paging flag splits the retrieval of a (possibly huge) number entities into pages. - -# Syntax - - - flag = name, [":", value]; - name = "P"; - value = [ index ], ["L", length]]; - index = ? any positive integer ?; - length = ? any positive integer ?; - -# Semantics - -The `index` (starting with zero) denotes the index of the first entity to be retrieved. The `length` is the number of entities on that page. If `length` is omitted, the default number of entities is returned (as configured by a server constant called ...). If only the `name` is given the paging behaves as if the `index` has been zero. - -# Examples - -`https://caosdb/Entities/all?flags=P:24L50` returns 50 entities starting with the 25th entity which would be retrieved without paging. - -`https://caosdb/Entities/all?flags=P:24` returns the default number of entities starting with the 25th entity which would be retrieved without paging. - -`https://caosdb/Entities/all?flags=P:L50` returns 50 entities starting with the first entity which would be retrieved without paging. - -`https://caosdb/Entities/all?flags=P` returns the default number of entities starting with the first entity which would be retrieved without paging. \ No newline at end of file diff --git a/doc/Query.md b/doc/Query.md deleted file mode 100644 index 1a875bf3008be0ef2b078ccae2b713d533acfaf4..0000000000000000000000000000000000000000 --- a/doc/Query.md +++ /dev/null @@ -1,355 +0,0 @@ - -# Example queries - -## Simple FIND Query -The following query will return any entity which has the name _ename_ and all its children. -`FIND ename` - -The following queries are equivalent and will return any entity which has the name _ename_ and all its children, but only if they are genuin records. Of course, the returned set of entities (henceforth referred to as _resultset_) can also be restricted to recordtypes, properties and files. - -`FIND RECORD ename` - -`FIND RECORDS ename` - -Wildcards use `*` for any characters or none at all. Wildcards for single characters (like the '_' wildcard from mysql) are not implemented yet. - -`FIND RECORD en*` returns any entity which has a name beginning with _en_. - -Regular expressions must be surrounded by _<<_ and '>>': - -`FIND RECORD <<e[aemn]{2,5}>>` - -`FIND RECORD <<[cC]am_[0-9]*>>` - -*TODO* (Timm): -Describe escape sequences like `\\`, `\*`, `\<<` and `\>>`. - -Currently, wildcards and regular expressions are only available for the _simple-find-part_ of the query, i. e. no wildcards/regexps for filters. - -## Simple COUNT Query - -This query counts entities which have certain properties. - -`COUNT ename` -will return the number of entities which have the name _ename_ and all their children. - -The syntax of the COUNT queries is equivalent to the FIND queries in any respect (this also applies to wildcards and regular expressions) but one: The prefix is to be `COUNT` instead of `FIND`. - -Unlike the FIND queries, the COUNT queries do not return any entities. The result of the query is the number of entities which _would be_ returned if the query was a FIND query. - -## Filters - -### POV - Property-Operator-Value - -The following queries are equivalent and will restrict the result set to entities which have a property named _pname1_ that has a value _val1_. - -`FIND ename.pname1=val1` - -`FIND ename WITH pname1=val1` - -`FIND ename WHICH HAS A PROPERTY pname1=val1` - -`FIND ename WHICH HAS A pname1=val1` - -Again, the resultset can be restricted to records: - -`FIND RECORD ename WHICH HAS A pname1=val1` - -_currently known operators:_ `=, !=, <=, <, >=, >` (and cf. next paragraphes!) - -#### Special Operator: LIKE - -The _LIKE_ can be used with wildcards. The `*` is a wildcard for any (possibly empty) sequence of characters. Examples: - -`FIND RECORD ename WHICH HAS A pname1 LIKE va*` - -`FIND RECORD ename WHICH HAS A pname1 LIKE va*1` - -`FIND RECORD ename WHICH HAS A pname1 LIKE *al1` - -_Note:_ The _LIKE_ operator is will only produce expectable results with text properties. - -#### Special Case: References - -In general a reference can be addressed just like a POV filter. So - -`FIND ename1.pname1=ename2` - -will also return any entity named _ename1_ which references the entity with name or id _ename2_ via a reference property named _pname1_. However, it will also return any entity with a text property of that name with the string value _ename2_. In order to restrict the result set to reference properties one may make use of special reference operators: - -_reference operators:_ `->, REFERENCES, REFERENCE TO` - - -The query looks like this: - -`FIND ename1 WHICH HAS A pname1 REFERENCE TO ename2` - -`FIND ename1 WHICH HAS A pname1->ename2` - -#### Time Special Case: DateTime - -_DateTime operators:_ `=, !=, <, >, IN, NOT IN` - -##### `d1=d2`: Equivalence relation. -* ''True'' iff d1 and d2 are equal in every respect (same DateTime flavor, same fields are defined/undefined and all defined fields are equal respectively). -* ''False'' iff they have the same DateTime flavor but have different fields defined or fields with differing values. -* ''Undefined'' otherwise. - -Examples: -* `2015-04-03=2015-04-03T00:00:00` is undefined. -* `2015-04-03T00:00:00=2015-04-03T00:00:00.0` is undefined (second precision vs. nanosecond precision). -* `2015-04-03T00:00:00.0=2015-04-03T00:00:00.0` is true. -* `2015-04-03T00:00:00=2015-04-03T00:00:00` is true. -* `2015-04=2015-05` is false. -* `2015-04=2015-04` is true. - -##### `d1!=d2`: Intransitive, symmetric relation. -* ''True'' iff `d1=d2` is false. -* ''False'' iff `d1=d2` is true. -* ''Undefined'' otherwise. - -Examples: -* `2015-04-03!=2015-04-03T00:00:00` is undefined. -* `2015-04-03T00:00:00!=2015-04-03T00:00:00.0` is undefined. -* `2015-04-03T00:00:00.0!=2015-04-03T00:00:00.0` is false. -* `2015-04-03T00:00:00!=2015-04-03T00:00:00` is false. -* `2015-04!=2015-05` is true. -* `2015-04!=2015-04` is false. - -##### `d1>d2`: Transitive, non-symmetric relation. -Semantics depend on the flavors of d1 and d2. If both are... -###### [UTCDateTime](Datatype#datetime) -* ''True'' iff the time of d1 is after the the time of d2 according to [https://en.wikipedia.org/wiki/Coordinated_Universal_Time](UTC) -* ''False'' otherwise. - -###### [SemiCompleteDateTime](Datatype#datetime) -* ''True'' iff `d1.ILB>d2.EUB` is true or `d1.ILB=d2.EUB` is true. -* ''False'' iff `d1.EUB<d2.ILB}} is true or {{{d1.EUB=d2.ILB` is true. -* ''Undefined'' otherwise. - -Examples: -* `2015>2014` is true. -* `2015-04>2014` is true. -* `2015-01-01T20:15.00>2015-01-01T20:14` is true. -* `2015-04>2015` is undefined. -* `2015>2015-04` is undefined. -* `2015-01-01T20:15>2015-01-01T20:15:15` is undefined. -* `2014>2015` is false. -* `2014-04>2015` is false. -* `2014-01-01>2015-01-01T20:15:30` is false. - -##### `d1<d2`: Transitive, non-symmetric relation. -Semantics depend on the flavors of d1 and d2. If both are... -###### [UTCDateTime](Datatype#datetime) -* ''True'' iff the time of d1 is before the the time of d2 according to [UTC](https://en.wikipedia.org/wiki/Coordinated_Universal_Time) -* ''False'' otherwise. - -###### [SemiCompleteDateTime](Datatype#datetime) -* ''True'' iff `d1.EUB<d2.ILB` is true or `d1.EUB=d2.ILB` is true. -* ''False'' iff `d1.ILB>d2.EUB}} is true or {{{d1.ILB=d2.EUB` is true. -* ''Undefined'' otherwise. - -Examples: -* `2014<2015` is true. -* `2014-04<2015` is true. -* `2014-01-01<2015-01-01T20:15:30` is true. -* `2015-04<2015` is undefined. -* `2015<2015-04` is undefined. -* `2015-01-01T20:15<2015-01-01T20:15:15` is undefined. -* `2015<2014` is false. -* `2015-04<2014` is false. -* `2015-01-01T20:15.00<2015-01-01T20:14` is false. - -##### `d1 IN d2`: Transitive, non-symmetric relation. -Semantics depend on the flavors of d1 and d2. If both are... -###### [SemiCompleteDateTime](Datatype#datetime) -* ''True'' iff (`d1.ILB>d2.ILB` is true or `d1.ILB=d2.ILB` is true) and (`d1.EUB<d2.EUB` is true or `d1.EUB=d2.EUB` is true). -* ''False'' otherwise. - -Examples: -* `2015-01-01 IN 2015` is true. -* `2015-01-01T20:15:30 IN 2015-01-01` is true. -* `2015-01-01T20:15:30 IN 2015-01-01T20:15:30` is true. -* `2015 IN 2015-01-01` is false. -* `2015-01-01 IN 2015-01-01T20:15:30` is false. - -##### `d1 NOT IN d2`: Transitive, non-symmetric relation. -Semantics depend on the flavors of d1 and d2. If both are... -###### [SemiCompleteDateTime](Datatype#datetime) -* ''True'' iff (`d1.ILB IN d2.ILB` is false. -* ''False'' otherwise. - -Examples: -* `2015 NOT IN 2015-01-01` is true. -* `2015-01-01 NOT IN 2015-01-01T20:15:30` is true. -* `2015-01-01 NOT IN 2015` is false. -* `2015-01-01T20:15:30 NOT IN 2015-01-01` is false. -* `2015-01-01T20:15:30 NOT IN 2015-01-01T20:15:30` is false. - -##### Note -These semantics follow a three-valued logic with ''true'', ''false'' and ''undefined'' as truth values. Only ''true'' is truth preserving. I.e. only those expressions which evaluate to ''true'' pass the POV filter. `FIND ... WHICH HAS A somedate=2015-01` only returns entities for which `somedate=2015-01` is true. On the other hand, `FIND ... WHICH DOESN'T HAVE A somedate=2015-01` returns entities for which `somedate=2015-01` is false or undefined. Shortly put, `NOT d1=d2` is not equivalent to `d1!=d2`. The latter assertion is stronger. - -#### Omitting the Property or the Value - -One doesn't have to specify the property or the value at all. The following query filters the result set for entities which have any property with a value greater than _val1_. - -`FIND ename WHICH HAS A PROPERTY > val1` - -`FIND ename . > val1` - -`FIND ename.>val1` - - -And for references... - -`FIND ename1 WHICH HAS A REFERENCE TO ename2` - -`FIND ename1 WHICH REFERENCES ename2` - -`FIND ename1 . -> ename2` - -`FIND ename1.->ename2` - - -The following query returns entities which have a _pname1_ property with any value. - -`FIND ename WHICH HAS A PROPERTY pname1` - -`FIND ename WHICH HAS A pname1` - -`FIND ename WITH pname1` - -`FIND ename WITH A pname1` - -`FIND ename WITH A PROPERTY pname1` - -`FIND ename WITH PROPERTY pname1` - -`FIND ename . pname1` - -`FIND ename.pname1` - -### TransactionFilter - -*Definition* - sugar:: `HAS BEEN` | `HAVE BEEN` | `HAD BEEN` | `WAS` | `IS` - negated_sugar:: `HAS NOT BEEN` | `HASN'T BEEN` | `WAS NOT` | `WASN'T` | `IS NOT` | `ISN'T` | `HAVN'T BEEN` | `HAVE NOT BEEN` | `HADN'T BEEN` | `HAD NOT BEEN` - by_clause:: `BY (ME | username | SOMEONE ELSE (BUT ME)? | SOMEONE ELSE BUT username)` - datetime:: A datetime string of the form `YYYY[-MM[-DD(T| )[hh[:mm[:ss[.nnn][(+|-)zzzz]]]]]]` - time_clause:: `[AT|ON|IN|BEFORE|AFTER|UNTIL|SINCE] (datetime) ` - -`FIND ename WHICH (sugar|negated_sugar)? (NOT)? (CREATED|INSERTED|UPDATED) (by_clause time_clause?| time_clause by_clause?)` - -*Examples* - -`FIND ename WHICH HAS BEEN CREATED BY ME ON 2014-12-24` - -`FIND ename WHICH HAS BEEN CREATED BY SOMEONE ELSE ON 2014-12-24` - -`FIND ename WHICH HAS BEEN CREATED BY erwin ON 2014-12-24` - -`FIND ename WHICH HAS BEEN CREATED BY SOMEONE ELSE BUT erwin ON 2014-12-24` - -`FIND ename WHICH HAS BEEN CREATED BY erwin` - -`FIND ename WHICH HAS BEEN INSERTED SINCE 2021-04` - -Note that `SINCE` and `UNTIL` are inclusive, while `BEFORE` and `AFTER` are not. - - -### File Location - -Search for file objects by their location: - -`FIND FILE WHICH IS STORED AT a/certain/path/` - -#### Wildcards - -_STORED AT_ can be used with wildcards similar to unix wildcards. - * `*` matches any characters or none at all, but not the directory separator `/` - * `**` matches any character or none at all. - * A leading `*` is short cut for `/**` - * A star directly between two other stars is ignored: `***` is the same as `**`. - * Escape character: `\` (E.g. `\\` is a literal backslash. `\*` is a literal star. But `\\*` is a literal backslash followed by a wildcard.) - -Examples: - -Find any files ending with `.acq`: -`FIND FILE WHICH IS STORED AT *.acq` or -`FIND FILE WHICH IS STORED AT **.acq` or -`FIND FILE WHICH IS STORED AT /**.acq` - -Find files stored one directory below `/data/`, ending with `.acq`: -`FIND FILE WHICH IS STORED AT /data/*/*.acq` - -Find files stored in `/data/`, ending with `.acq`: -`FIND FILE WHICH IS STORED AT /data/*.acq` - -Find files stored in a directory at any depth in the tree below `/data/`, ending with `.acq`: -`FIND FILE WHICH IS STORED AT /data/**.acq` - -Find any file in a directory which begins with `2016-02`: -`FIND FILE WHICH IS STORED AT */2016-02*/*` - - -### Back References - -The back reference filters for entities that are referenced by another entity. The following query returns entities of the type _ename1_ which are referenced by _ename2_ entities via the reference property _pname1_. - -* `FIND ename1 WHICH IS REFERENCED BY ename2 AS A pname1` -* `FIND ename1 WITH @ ename2 / pname1` -* `FIND ename1 . @ ename2 / pname1` - -One may omit the property specification: - -* `FIND ename1 WHICH IS REFERENCED BY ename2` -* `FIND ename1 WHICH HAS A PROPERTY @ ename2` -* `FIND ename1 WITH @ ename2` -* `FIND ename1 . @ ename2` - -### Combining Filters with Propositional Logic - -Any result set can be filtered by logically combining POV filters or back reference filters: - -#### Conjunction (AND) - -* `FIND ename1 WHICH HAS A PROPERTY pname1=val1 AND A PROPERTY pname2=val2 AND A PROPERTY...` -* `FIND ename1 WHICH HAS A PROPERTY pname1=val1 AND A pname2=val2 AND ...` -* `FIND ename1 . pname1=val1 & pname2=val2 & ...` - -#### Disjunction (OR) - -* `FIND ename1 WHICH HAS A PROPERTY pname1=val1 OR A PROPERTY pname2=val2 Or A PROPERTY...` -* `FIND ename1 WHICH HAS A PROPERTY pname1=val1 OR A pname2=val2 OR ...` -* `FIND ename1 . pname1=val1 | pname2=val2 | ...` - -#### Negation (NOT) - -* `FIND ename1 WHICH DOES NOT HAVE A PROPERTY pname1=val1` -* `FIND ename1 WHICH DOESN'T HAVE A pname1=val1` -* `FIND ename1 . NOT pname2=val2` -* `FIND ename1 . !pname2=val2` - -#### ... and combinations with parentheses - -* `FIND ename1 WHICH HAS A pname1=val1 AND DOESN'T HAVE A pname2<val2 AND ((WHICH HAS A pname3=val3 AND A pname4=val4) OR DOES NOT HAVE A (pname5=val5 AND pname6=val6))` -* `FIND ename1 . pname1=val1 & !pname2<val2 & ((pname3=val3 & pname4=val4) | !(pname5=val5 & pname6=val6))` -* `FIND ename1.pname1=val1&!pname2<val2&((pname3=val3&pname4=val4)|!(pname5=val5&pname6=val6))` - -### A Few Important Expressions - -* A:: The indistinct article. This is only syntactic suger. Equivalent expressions: `A, AN` -* AND:: The logical _and_. Equivalent expressions: `AND, &` -* FIND:: The beginning of the query. -* NOT:: The logical negation. Equivalent expressions: `NOT, DOESN'T HAVE A PROPERTY, DOES NOT HAVE A PROPERTY, DOESN'T HAVE A, DOES NOT HAVE A, DOES NOT, DOESN'T, IS NOT, ISN'T, !` -* OR:: The logical _or_. Equivalent expressions: `OR, |` -* RECORD,RECORDTYPE,FILE,PROPERTY:: Role expression for restricting the result set to a specific role. -* WHICH:: The marker for the beginning of the filters. Equivalent expressions: `WHICH, WHICH HAS A, WHICH HAS A PROPERTY, WHERE, WITH (A), .` -* REFERENCE:: This one is tricky: `REFERENCE TO` expresses a the state of _having_ a reference property. `REFERENCED BY` expresses the state of _being_ referenced by another entity. -* COUNT:: `COUNT` works like `FIND` but doesn't return the entities. - -# Future - - * *Sub Queries* (or *Sub Properties*): `FIND ename WHICH HAS A pname WHICH HAS A subpname=val`. This is like: `FIND AN experiment WHICH HAS A camera WHICH HAS A 'serial number'= 1234567890` - * *More Logic*, especially `ANY`, `ALL`, `NONE`, and `SUCH THAT` key words (and equivalents) for logical quantisation: `FIND ename1 SUCH THAT ALL ename2 WHICH HAVE A REFERENCE TO ename1 HAVE A pname=val`. This is like `FIND experiment SUCH THAT ALL person WHICH ARE REFERENCED BY THIS experiment AS conductor HAVE AN 'academic title'=professor.` - diff --git a/doc/User_Administration.md b/doc/User_Administration.md deleted file mode 100644 index a51894cd87126b07f56addccba3f635e04872d71..0000000000000000000000000000000000000000 --- a/doc/User_Administration.md +++ /dev/null @@ -1,34 +0,0 @@ -Author: Timm Fitschen - -Email: timm.fitschen@ds.mpg.de - -Date: 2013-02-23 - -# No Proposal -http://caosdb/register - -# Proposal - -## Add User - -* POST Request is to be send to `http://host:port/User`. -* This requires authetication as user _admin_ (default password: _adminpw_). -* Http body: - - - <Post> - <User name="${username}" password="${md5ed_password} /> - </Post> - -## Delete User - -* DELETE Request -* admin authentication required. -* Http body: - - - <Delete> - <User name="${username}/> - </Delete> - -The user to be deleted may also be identified by his id (`id="${id}"`) instead of his name. diff --git a/doc/devel/Benchmarking.md b/doc/devel/Benchmarking.md deleted file mode 100644 index 8a3eff2addb927eab425f7e755e3a181a53b9d18..0000000000000000000000000000000000000000 --- a/doc/devel/Benchmarking.md +++ /dev/null @@ -1,316 +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 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` ### - -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). - -You might consider to increase `performance_schema_events_transactions_history_long_size`. -``` -performance_schema_events_transactions_history_long_size=1000000 -``` -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`). - -- Single timings can be added to instances of that class via the - `addBenchmark(object, time)` method. Multiple benchmarks for the same object - (typically just strings) can be averaged. -- Benchmarks can be serialized into XML, `Container` and `Query` objects already - use this with their included benchmarks to output benchmarking results. -- To work with the benchmarks of often used objects, use these methods: - - `Container.getTransactionBenchmark().addBenchmark()` - - `Query.addBenchmark()` - - -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 ## - -- `Transaction.execute()` :: Logs benchmarks for events like: - - `INIT` :: The transaction's `init()` method. - - `PRE_CHECK` - - `CHECK` - - `POST_CHECK` - - `PRE_TRANSACTION` - - `TRANSACTION` -> typically calls - `database.backend.transaction.[BackendTransaction].execute()`, which in turn - calls, some levels deeper, `backend.transaction.....execute(<k extends - BackendTransaction> t)` -> see next point - - ... -- `backend.transaction.[...].execute(transaction)` :: This method is benchmarked - again (via parent class `BackendTransaction`), this is probably the deepest - level of benchmarking currently (Benchmark is logged as - e.g. `<RetrieveFullEntity>...</>`). It finally calls - `[MySQLTransaction].execute()`. -- `[MySQLTransaction].execute()` :: This is the deepest backend implementation - part, it typically creates a prepared statement and executes it. -- Currently not benchmarked separately: - - Getting the actual implementation (probably fast?) - - Preparing the SQL statement - - Executing the SQL statement - - Java-side caching - -## 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/doc/devel/Development.md b/doc/devel/Development.md deleted file mode 100644 index 246f80db14eeadfd1b95e062f0b43636d5f6960c..0000000000000000000000000000000000000000 --- a/doc/devel/Development.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Development -author: Daniel Hornung -... - - -# Developing the CaosDB server # -This file contains information about server development, it is aimed at those -who want to debug, understand or enhance the CaosDB server. - -## Testing ## -Whether developing new features, refacturing code or fixing bugs, the server -code should be thoroughly tested for correct and incorrect behvaiour, on correct -and incorrect input. - -### Writing tests ### -Tests go into `src/test/java/caosdb/`, the files there can serve as examples for -writing tests. - -### Running tests with Maven ### -- Automatic testing can be done with `make test` or, after compilation, `mvn - test`. -- Tests of single modules can be started with `mvn test -Dtest=TestClass` -- Test of a single method `footest`: `mvn test -Dtest=TestClass#footest` diff --git a/doc/devel/Logging.md b/doc/devel/Logging.md deleted file mode 100644 index f9b1680e61b207379ff513e4828befca5df8ec31..0000000000000000000000000000000000000000 --- a/doc/devel/Logging.md +++ /dev/null @@ -1,67 +0,0 @@ -# Logging - -## Framework - -We use the SLF4J API with a log4j2 backend for all of our Code. Please do not use log4j2 directly or any other logging API. - -Note that some libraries on the classpath use the `java.util.logging` API and log4j1 logging framework instead. These loggers cannot be configurated with the help of this README by now. - -## Configuration - -The configuration of the log4j2 backend is done via `properties` files which -comply with the [log4j2 -specifications](https://logging.apache.org/log4j/2.x/manual/configuration.html#Properties). -XML, YAML, or JSON files are not supported. The usual mechanisms for automatic -configuration with such files is disabled. Instead, files have to be placed -into the `conf` subdirs, as follows: - -### Default and Debug Logging - -The default configuration is located at `conf/core/log4j2-default.properties`. For the debug mode, the configuration from `conf/core/log4j2-debug.properties` is merged with the default configuration. These files should not be changed by the user. - -### User Defined Logging - -The default and debug configuration can be overridden by the user with `conf/ext/log4j2.properties` and any file in the directory `conf/ext/log4j2.properties.d/` which is suffixed by `.properties`. All loggin configuration files are merged using the standard merge strategy of log4: - -> # Composite Configuration -> Log4j allows multiple configuration files to be used by specifying them as a list of comma separated file paths on log4j.configurationFile. The merge logic can be controlled by specifying a class that implements the MergeStrategy interface on the log4j.mergeStrategy property. The default merge strategy will merge the files using the following rules: -> 1. The global configuration attributes are aggregated with those in later configurations replacing those in previous configurations, with the exception that the highest status level and the lowest monitorInterval greater than 0 will be used. -> 2. Properties from all configurations are aggregated. Duplicate properties replace those in previous configurations. -> 3. Filters are aggregated under a CompositeFilter if more than one Filter is defined. Since Filters are not named duplicates may be present. -> 4. Scripts and ScriptFile references are aggregated. Duplicate definiations replace those in previous configurations. -> 5. Appenders are aggregated. Appenders with the same name are replaced by those in later configurations, including all of the Appender's subcomponents. -> 6. Loggers are all aggregated. Logger attributes are individually merged with duplicates being replaced by those in later configurations. Appender references on a Logger are aggregated with duplicates being replaced by those in later configurations. Filters on a Logger are aggregated under a CompositeFilter if more than one Filter is defined. Since Filters are not named duplicates may be present. Filters under Appender references included or discarded depending on whether their parent Appender reference is kept or discarded. - -[2](https://logging.apache.org/log4j/2.x/manual/configuration.html#CompositeConfiguration) - -## Some Details and Examples - -### Make Verbose - -To make the server logs on the console more verbose, insert `rootLogger.level = DEBUG` or even `rootLogger.level = TRACE` into a properties file in the `conf/ext/log4j2.properties.d/` directory or the `conf/ext/log4j2.properties` file. - -### Log Directory - -By default, log files go to `./log/`, e.g. `./log/request_errors/current.log`. The log directory in `DEBUG_MODE` is located at `./testlog/`. - -To change that, insert `property.LOG_DIR = /path/to/my/logs` into a properties file in the `conf/ext/log4j2.properties.d/` directory or the `conf/ext/log4j2.properties` file - -### Special loggers - -* `REQUEST_ERRORS_LOGGER` for logging server errors with SRID, full request and full response. WARNING: This logger stores unencrypted content of request with possibly confidential content. -* `REQUEST_TIME_LOGGER` for timing the requests. - -These loggers are defined in the `conf/core/log4j2-default.properties` file. - -#### Enable Request Time Logger - -The `REQUEST_TIME_LOGGER` is disabled by default, its log level is set to `OFF`. To enable it and write logs to the directory denoted by `property.LOG_DIR`, create a `properties` file under `conf/ext/log4j2.properties.d/` which contains at least - -```properties -property.REQUEST_TIME_LOGGER_LEVEL = TRACE -``` - -### debug.log - -When in `DEBUG_MODE`, e.g. when started with `make run-debug`, the server also writes all logs to `debug.log` in the log directory. - diff --git a/doc/faq.md b/doc/faq.md deleted file mode 100644 index 09e8adb7f3072ea94042e43d962c33345c2482d6..0000000000000000000000000000000000000000 --- a/doc/faq.md +++ /dev/null @@ -1,43 +0,0 @@ - - -# How do I declare a LIST property? - -Use the datatype parameter (available with Property constructors and with the ```add_property``` method and the ```LIST``` function. - -```python -#with constructor -p = caosdb.Property(name="ListOfDoubles", datatype=caosdb.LIST(caosdb.DOUBLE)) - -# with add_property method -my_entity.add_property(name="ListOfIntegers", datatype=caosdb.LIST(caosdb.INTEGER)) -my_entity.add_property(name="ListOfThings", datatype=caosdb.LIST("Thing")) -my_entity.add_property(name="ListOfThings", datatype=caosdb.LIST(caosdb.RecordType('Thing')) -``` - -# Which data types are there? - -There are 7 basic data types: - -* `INTEGER` -* `DOUBLE` -* `DATETIME` -* `TEXT` -* `BOOLEAN` -* `FILE` -* `REFERENCE` - -There is (so far) 1 data type for collections: - -* `LIST` (Well, LIST-of-another-data-type, e.g. `LIST(INTEGER)`) - -And furthermore,... - -* Any RecordType can be used as a `REFERENCE` data type with a limited scope. That is, a property - - ```python - p = caosdb.Property(name="Conductor", datatype="Person") - ``` - - will only accept those Entities as value which have a "Person" RecordType as a direct or indirect parent. - -See also: [Datatype](Datatype) diff --git a/pom.xml b/pom.xml index b251820a27733901e47012ecc9b9a4fb682b0229..abe5e2827883f9445f02505294881134aefb4c1f 100644 --- a/pom.xml +++ b/pom.xml @@ -25,7 +25,7 @@ <modelVersion>4.0.0</modelVersion> <groupId>org.caosdb</groupId> <artifactId>caosdb-server</artifactId> - <version>0.9.1-SNAPSHOT</version> + <version>0.13.0-SNAPSHOT</version> <packaging>jar</packaging> <name>CaosDB Server</name> <scm> @@ -381,9 +381,9 @@ </executions> </plugin> <plugin> - <groupId>com.coveo</groupId> + <groupId>com.spotify.fmt</groupId> <artifactId>fmt-maven-plugin</artifactId> - <version>2.5.1</version> + <version>2.21.1</version> <configuration> <skip> <!-- Set skip to `true` to prevent auto-formatting while coding. --> diff --git a/requirements-docs.txt b/requirements-docs.txt new file mode 100644 index 0000000000000000000000000000000000000000..d60cf6116e6e2a19e5422eb86979c96d54652869 --- /dev/null +++ b/requirements-docs.txt @@ -0,0 +1,4 @@ +sphinx-rtd-theme +sphinxcontrib-plantuml +javasphinx +sphinx-a4doc diff --git a/src/doc/CaosDB-Query-Language.md b/src/doc/CaosDB-Query-Language.md index a34191d051fec9907a6cbb47f11f97d60ac972ac..6cbe64f1582b53f9e5c6dc60e21d988fd193d476 100644 --- a/src/doc/CaosDB-Query-Language.md +++ b/src/doc/CaosDB-Query-Language.md @@ -2,61 +2,102 @@ See syntax specification in [CaosDB Query Language Syntax](query-syntax). -## Simple FIND Query - -The following query will return any record which has the name _somename_ and all -record children of any entity with that name. - -`FIND somename` - -On server in the default configuration, the following queries are equivalent to this one. - -`FIND RECORD somename` - -`FIND RECORDS somename` - -Of course, the returned set of entities (henceforth referred to as _resultset_) can also be -restricted to RecordTypes (`FIND RECORDTYPE ...`), Properties (`FIND PROPERTY ...`) and Files (`FIND -FILE ...`). - -You can include all entities (Records, RecordTypes, Properties, ...) into the results by using the -`ENTITY` keyword: - -`FIND ENTITY somename` - -Wildcards use `*` for any characters or none at all. Wildcards for single characters (like the `_` wildcard from mysql) are not implemented yet. - -`FIND en*` returns any record which has a name beginning with _en_. - -Regular expressions must be surrounded by _<<_ and _>>_: - -`FIND <<e[aemn]{2,5}>>` - -`FIND <<[cC]amera_[0-9]*>>` - -*TODO*: -Describe escape sequences like `\\\\ `, `\*`, `\<<` and `\>>`. - -Currently, wildcards and regular expressions are only available for the _simple-find-part_ of the -query, i.e. not for property-operator-value filters (see below). - -## Simple COUNT Query - -COUNT queries count entities which have certain properties. - -`COUNT ... rname ...` - -will return the number of records which have the name _rname_ and all record -children of any entity with that name. - -The syntax of the COUNT queries is equivalent to the FIND queries in any -respect (this also applies to wildcards and regular expressions) but one: The -prefix is to be `COUNT` instead of `FIND`. - -Unlike the FIND queries, the COUNT queries do not return any entities. The result of the query is -the number of entities which _would be_ returned if the query was a FIND query. - -## Filters +In this chapter, the CaosDB Query Language (CQL) is presented as a means of +formulating search commands, commonly referred to as queries. It is highly +recommended that you experiment with the examples provided, such as those found +on https://demo.indiscale.com. An interactive tour is also available on this +public instance, which includes a comprehensive overview of the query language. +Therefore, it is suggested that you begin there and subsequently proceed with +this more detailed explanation. + +## Introduction + +Queries typically start with the keyword `FIND`, followed by a description of +what you want to find. For example, you can search for all musical instruments +with `FIND MusicalInstrument`. + +*Note*, the CQL is case **in**sensitive. We will write keywords of CQL in all +caps to illustrate what parts are part of the language. + +The most common way is to provide a RecordType name after `FIND` (as in the +example above). However, you can also use the name of some other entity: +`FIND 'My first guitar'`. + +*Note*, that we put the name here in quotes. Spaces are used in CQL as separator +of words. Thus, if something contains quotes, like the name here, it needs to be +quoted. + +While queries like the last one are great to get an impression of the data, +often we need to be more specific. Therefore, queries can include various +conditions to restrict the result set. + +Example: `FIND MusicalAnalysis WITH quality_factor>0.5 AND date IN +2019`. The keyword `WITH` signifies that for each Record of the type +`MusicalAnalysis`, an assessment is made to determine whether it possesses a +Property labelled `quality_factor` that exceeds 0.5, as well as another +Property labelled `date` that may correspond to any day within the year 2019. + +In order to make CQL easier to learn and to remember we designed it to be close +to natural spoken English language. For example, you can write +`FIND Guitar WHICH HAS A PROPERTY price`. Here, "HAS A PROPERTY" is what we call +syntactic sugar. It lets the query role off the tongue more easily than +`FIND Guitar WHICH price` but it is actually not needed and does not change +the meaning of the query. In fact, you could also write `FIND Guitar WITH +price`. + +If you are only interested in the number of Entities that match your query, you +can replace `FIND` with `COUNT` and the query will only return the number of +Entities in the result set. + +Sometimes the list of Records that you get using a `FIND` query is not what you +need; especially if you want to export a subset of the data for the analysis +with some external tool. +`SELECT` queries offer to represent the query result in a tabular form. + +If you replace the `FIND` keyword of a query with `SELECT x, y, z FROM`, then +CaosDB will return the result as tabular data. + +For example, instead of `FIND Guitar`, try out +`SELECT name, electric FROM Guitar` + +As you can see, those queries are design to allow very specific data requests. +If you do not want/need to be as specific you can omit the first keyword (`FIND` +or `SELECT`) which creates a search for anything that has a text Property with +something like your expression. For example, the query "John" will search for +any Records that has a text property that contains this string. + +With this, we conclude our introduction of CQL. You now know about the basic +elements. The following will cover the various aspects in more detail and you +will for example learn how you can use references among Records, or meta data +like the creation time of a Record to restrict the query result set. + +## What am I searching for? + +We already learned, that we can provide the name of a RecordType after the `FIND` +keyword. Let's call this part of the query "entity expression". In general, we +need to identify with the entity expression one or more entities via their name, CaosDB ID +or a pattern. +- `FIND Guitar` +- `FIND Guit*` ('*' represents none, one or more characters) +- `FIND <<[gG]ui.*>>` (a regular expression surrounded by _<<_ and '>>'. see below) +- `FIND 110` + +The result set will contain Entities that are either identified by the entity expression +directly (i.e. they have the name or the given ID) or the have such an Entity as +parent. + +As you know, CaosDB distincts among different Entity roles: +- Entity +- Record +- RecordType +- Property +- File + +You can provide the role directly after the `FIND` keyword and before the +entity expression: `FIND RECORD Guitar`. The result set will then restricted +to Entities with that role. + +## Conditions / Filters ### POV - Property-Operator-Value @@ -135,15 +176,19 @@ Examples: * `2015-04-03T00:00:00.0!=2015-04-03T00:00:00.0` is false. * `2015-04-03T00:00:00!=2015-04-03T00:00:00` is false. * `2015-04!=2015-05` is true. -* `2015-04!=2015-04` is false +* `2015-04!=2015-04` is false. ##### `d1>d2`: Transitive, non-symmetric relation. -Semantics depend on the flavors of d1 and d2. If both are... -###### [UTCDateTime](specification/Datatype.html#datetime) + +Semantics depend on the flavors of d1 and d2. If both are... + +###### [UTCDateTime](specification/Datatype.html#datetime) + * ''True'' iff the time of d1 is after the the time of d2 according to [UTC](https://en.wikipedia.org/wiki/Coordinated_Universal_Time). * ''False'' otherwise. ###### [SemiCompleteDateTime](specification/Datatype.html#datetime) + * ''True'' iff `d1.ILB>d2.EUB` is true or `d1.ILB=d2.EUB` is true. * ''False'' iff `d1.EUB<d2.ILB}} is true or {{{d1.EUB=d2.ILB` is true. * ''Undefined'' otherwise. @@ -160,12 +205,16 @@ Examples: * `2014-01-01>2015-01-01T20:15:30` is false. ##### `d1<d2`: Transitive, non-symmetric relation. -Semantics depend on the flavors of d1 and d2. If both are... + +Semantics depend on the flavors of d1 and d2. If both are... + ###### [UTCDateTime](specification/Datatype.html#datetime) + * ''True'' iff the time of d1 is before the the time of d2 according to [UTC](https://en.wikipedia.org/wiki/Coordinated_Universal_Time) * ''False'' otherwise. ###### [SemiCompleteDateTime](specification/Datatype.html#datetime) + * ''True'' iff `d1.EUB<d2.ILB` is true or `d1.EUB=d2.ILB` is true. * ''False'' iff `d1.ILB>d2.EUB}} is true or {{{d1.ILB=d2.EUB` is true. * ''Undefined'' otherwise. @@ -182,8 +231,11 @@ Examples: * `2015-01-01T20:15.00<2015-01-01T20:14` is false. ##### `d1 IN d2`: Transitive, non-symmetric relation. + Semantics depend on the flavors of d1 and d2. If both are... + ###### [SemiCompleteDateTime](specification/Datatype.html#datetime) + * ''True'' iff (`d1.ILB>d2.ILB` is true or `d1.ILB=d2.ILB` is true) and (`d1.EUB<d2.EUB` is true or `d1.EUB=d2.EUB` is true). * ''False'' otherwise. @@ -195,8 +247,11 @@ Examples: * `2015-01-01 IN 2015-01-01T20:15:30` is false. ##### `d1 NOT IN d2`: Transitive, non-symmetric relation. + Semantics depend on the flavors of d1 and d2. If both are... + ###### [SemiCompleteDateTime](specification/Datatype.html#datetime) + * ''True'' iff `d1.ILB IN d2.ILB` is false. * ''False'' otherwise. @@ -208,7 +263,15 @@ Examples: * `2015-01-01T20:15:30 NOT IN 2015-01-01T20:15:30` is false. ##### Note -These semantics follow a three-valued logic with ''true'', ''false'' and ''undefined'' as truth values. Only ''true'' is truth preserving. I.e. only those expressions which evaluate to ''true'' pass the POV filter. `FIND ... WHICH HAS A somedate=2015-01` only returns entities for which `somedate=2015-01` is true. On the other hand, `FIND ... WHICH DOESN'T HAVE A somedate=2015-01` returns entities for which `somedate=2015-01` is false or undefined. Shortly put, `NOT d1=d2` is not equivalent to `d1!=d2`. The latter assertion is stronger. + +These semantics follow a three-valued logic with `true`, `false` and `undefined` as truth +values. Only `true` is truth preserving. I.e. only those expressions which evaluate to `true` +pass the POV filter. + +`FIND ... WHICH HAS A somedate=2015-01` only returns entities for which `somedate=2015-01` is +true. On the other hand, `FIND ... WHICH DOESN'T HAVE A somedate=2015-01` returns entities for which +`somedate=2015-01` is false or undefined. Shortly put, `NOT d1=d2` is not equivalent to +`d1!=d2`. The latter assertion is stronger. #### Omitting the Property or the Value @@ -240,6 +303,12 @@ The following query returns records which have a _pname1_ property with any valu `FIND ename WITH pname1` +`FIND ename WITH A pname1` + +`FIND ename WITH A PROPERTY pname1` + +`FIND ename WITH PROPERTY pname1` + `FIND ename . pname1` `FIND ename.pname1` @@ -338,24 +407,53 @@ Any result set can be filtered by logically combining POV filters or back refere #### Conjunction (AND) -* `FIND ename1 WHICH HAS A PROPERTY pname1=val1 AND A PROPERTY pname2=val2 AND A PROPERTY...` -* `FIND ename1 WHICH HAS A PROPERTY pname1=val1 AND A pname2=val2 AND ...` -* `FIND ename1 . pname1=val1 & pname2=val2 & ...` +As we saw above, we can combine conditions: + +`FIND MusicalAnalysis WHICH HAS quality_factor>0.5 AND date IN 2019` + +In general, the conjunction takes the form `FIND <eexpr> WHICH <filter1> AND <filter2>`. You can +also use `&` instead of `AND` or chain more than two conditions. If you mix conjunctions with +disjunctions, you need to add brackets to define the priority. For example: `FIND <eexpr> WHICH +(<filter1> AND <filter2>) OR <filter3>`. + +`FIND Guitar WHICH REFERENCES Manufacturer AND price` is a combination of a reference filter and a +POV filter. For readability, you can also write +`FIND Guitar WHICH REFERENCES Manufacturer AND WHICH HAS A price`. However, the additional "WHICH +HAS A" is purely cosmetic (syntactic sugar). #### Disjunction (OR) +The rules for disjunctions (`OR` or `|`) are the same as for conjunctions, see above. + * `FIND ename1 WHICH HAS A PROPERTY pname1=val1 OR A PROPERTY pname2=val2 Or A PROPERTY...` * `FIND ename1 WHICH HAS A PROPERTY pname1=val1 OR A pname2=val2 OR ...` * `FIND ename1 . pname1=val1 | pname2=val2 | ...` #### Negation (NOT) +You can negate any filter by prefixing the filter with `NOT` or `!`: +`FIND <eexpr> WHICH NOT <filter1>`. + +There are many syntactic sugar alternatives which are treated the same as "NOT": +- `DOES NOT HAVE` +- `ISN'T` +- and many more + * `FIND ename1 WHICH DOES NOT HAVE A PROPERTY pname1=val1` * `FIND ename1 WHICH DOESN'T HAVE A pname1=val1` * `FIND ename1 . NOT pname2=val2` * `FIND ename1 . !pname2=val2` -#### ... and combinations with parentheses +#### Parentheses +Basically, you can put parantheses around filter expressions and con- or +disjunctions. +- `FIND Guitar WHICH (REFERENCES Manufacturer AND WHICH HAS A price)`. +- `FIND Guitar WHICH (REFERENCES Manufacturer) AND (WHICH HAS A price)`. + +For better readability, the above query can be written as: +- `FIND Guitar WHICH (REFERENCES Manufacturer AND HAS A price)`. +Note, that without syntactic sugar this query looks like: +- `FIND Guitar WHICH (REFERENCES Manufacturer AND price)`. * `FIND ename1 WHICH HAS A pname1=val1 AND DOESN'T HAVE A pname2<val2 AND ((WHICH HAS A pname3=val3 AND A pname4=val4) OR DOES NOT HAVE A (pname5=val5 AND pname6=val6))` * `FIND ename1 . pname1=val1 & !pname2<val2 & ((pname3=val3 & pname4=val4) | !(pname5=val5 & pname6=val6))` @@ -369,7 +467,7 @@ Any result set can be filtered by logically combining POV filters or back refere * NOT:: The logical negation. Equivalent expressions: `NOT, DOESN'T HAVE A PROPERTY, DOES NOT HAVE A PROPERTY, DOESN'T HAVE A, DOES NOT HAVE A, DOES NOT, DOESN'T, IS NOT, ISN'T, !` * OR:: The logical _or_. Equivalent expressions: `OR, |` * RECORD,RECORDTYPE,FILE,PROPERTY:: Role expression for restricting the result set to a specific role. -* WHICH:: The marker for the beginning of the filters. Equivalent expressions: `WHICH, WHICH HAS A, WHICH HAS A PROPERTY, WHERE, WITH, .` +* WHICH:: The marker for the beginning of the filters. Equivalent expressions: `WHICH, WHICH HAS A, WHICH HAS A PROPERTY, WHERE, WITH (A), .` * REFERENCE:: This one is tricky: `REFERENCE TO` expresses a the state of _having_ a reference property. `REFERENCED BY` expresses the state of _being_ referenced by another entity. * COUNT:: `COUNT` works like `FIND` but doesn't return the entities. @@ -420,7 +518,7 @@ would return any entity with that name and all children, regardless of the entity's role. Basically, `FIND ename` *was* equivalent to `FIND ENTITY ename`. Since 0.9.0 the default behavior has changed and now `FIND ename` is equivalent to `FIND RECORD ename`. This default is, however, configurable via the -`FIND_QUERY_DEFAULT_ROLE` server property. See [Server Configuration](./administration/configuration.rst). +`FIND_QUERY_DEFAULT_ROLE` server property. See [Server Configuration](./administration/configuration). ## Future diff --git a/src/doc/Data-Model.md b/src/doc/Data-Model.md index d11a88f67141e366a28ed84895903df7ff062822..b471b8241301644b7484376a6e87f72c7f04d3bb 100644 --- a/src/doc/Data-Model.md +++ b/src/doc/Data-Model.md @@ -15,11 +15,10 @@ contain information about the Record. The following is a more detailed explanation (also see this [paper](https://www.mdpi.com/2306-5729/4/2/83)). -> Record Types and Abstract Properties are used to define the ontology -> for a particular domain in which the RDMS is used. Records are used -> to store the actual data and therefore represent individuals or -> particular things, e.g. a particular experiment, a particular time -> series, etc. +> Record Types and Abstract Properties are used to define the ontology for a particular domain in +> which the RDMS (research data management) is used. Records are used to store the actual data and +> therefore represent individuals or particular things, e.g. a particular experiment, a particular +> time series, etc. ## Record Types diff --git a/src/doc/FAQ.rst b/src/doc/FAQ.rst new file mode 100644 index 0000000000000000000000000000000000000000..54bfb296fa96e645034504e94368b34e76448b75 --- /dev/null +++ b/src/doc/FAQ.rst @@ -0,0 +1,57 @@ +==== +FAQs +==== + +These FAQs (frequently asked questions) can be extended, if *you* help us. Please `submit an issue +<https://gitlab.com/caosdb/caosdb-server/issues/new>`__ if you have a question that should be +answered here. + +.. contents:: Select your question: + :local: + +How do I declare a LIST property? +================================= + +Use the datatype parameter (available with Property constructors and +with the ``add_property`` method and the ``LIST`` function. + +.. code:: python + + # with constructor + p = caosdb.Property(name="ListOfDoubles", datatype=caosdb.LIST(caosdb.DOUBLE)) + + # with add_property method + my_entity.add_property(name="ListOfIntegers", datatype=caosdb.LIST(caosdb.INTEGER)) + my_entity.add_property(name="ListOfThings", datatype=caosdb.LIST("Thing")) + my_entity.add_property(name="ListOfThings", datatype=caosdb.LIST(caosdb.RecordType('Thing')) + +Which data types are there? +=========================== + +There are 7 basic data types: + +- ``INTEGER`` +- ``DOUBLE`` +- ``DATETIME`` +- ``TEXT`` +- ``BOOLEAN`` +- ``FILE`` +- ``REFERENCE`` + +There is (so far) 1 data type for collections: + +- ``LIST`` (Actually, LIST-of-another-data-type, e.g. ``LIST(INTEGER)``) + +And furthermore,… + +- Any RecordType can be used as a ``REFERENCE`` data type with a + limited scope. That is, a property + + .. code:: python + + p = caosdb.Property(name="Conductor", datatype="Person") + + will only accept those Entities as value which have a “Person†+ RecordType as a direct or indirect parent. + +See also: :any:`Datatype<specification/Datatype>`. diff --git a/src/doc/administration/server_side_scripting.rst b/src/doc/administration/server_side_scripting.rst index 6dc08999f38e1912e72440c609b8cde1ea7ce0bb..23beb74eaf5a6e56d1cc5c56bbd1f077306fc48c 100644 --- a/src/doc/administration/server_side_scripting.rst +++ b/src/doc/administration/server_side_scripting.rst @@ -83,3 +83,44 @@ An invocation via a button in javascript could look like: For more information see the :doc:`specification of the API <../specification/Server-side-scripting>` +Calling from the webui +--------------------- +Refer to `webui documentation <https://docs.indiscale.com//caosdb-webui/extension/forms.html#calling-a-server-side-script>`_ to learn how to setup the webui side of this interaction. + +The following example assumes that the form in the webui has only one filed +which is a file upload with the name ``csvfile``. + + +.. code-block:: python + + import os + import linkahead as db + from caosadvancedtools.serverside import helper + from caosadvancedtools.serverside.logging import configure_server_side_logging + + def main(): + parser = helper.get_argument_parser() + args = parser.parse_args() + db.configure_connection(auth_token=args.auth_token) + # setup logging and reporting if serverside execution + userlog_public, htmluserlog_public, debuglog_public = configure_server_side_logging() + + if not hasattr(args, "filename") or not args.filename: + raise RuntimeError("No file with form data provided!") + + # Read the input from the form (form.json) + with open(args.filename) as form_json: + form_data = json.load(form_json) + + # files are uploaded to this dicectory + upload_dir = os.path.dirname((args.filename)) + # Read content of th uplaoded file + csv_file_path = os.path.join(upload_dir, form_data["csvfile"]) + + # Do something with the upload csv file... + + + if __name__ == "__main__": + main() + + diff --git a/src/doc/conf.py b/src/doc/conf.py index 700a009370e0925a209efe66da04f18324beea69..530d8d653c6946b1c64738d7ad129f43d00afa8e 100644 --- a/src/doc/conf.py +++ b/src/doc/conf.py @@ -22,13 +22,13 @@ from os.path import dirname, abspath # -- Project information ----------------------------------------------------- project = 'caosdb-server' -copyright = '2022, IndiScale GmbH' +copyright = '2023, IndiScale GmbH' author = 'Daniel Hornung, Timm Fitschen' # The short X.Y version -version = '0.9.1' +version = '0.13.0' # The full version, including alpha/beta/rc tags -release = '0.9.1-SNAPSHOT' +release = '0.13.0-dev' # -- General configuration --------------------------------------------------- @@ -49,7 +49,7 @@ extensions = [ "sphinx.ext.autosectionlabel", # Allow reference sections using its title "sphinx_rtd_theme", "sphinxcontrib.plantuml", # PlantUML diagrams - "sphinx_a4doc", # antl4 + "sphinx_a4doc", # antlr4 ] # Add any paths that contain templates here, relative to this directory. diff --git a/src/doc/development/benchmarking.md b/src/doc/development/benchmarking.md deleted file mode 100644 index 0be781453a6f85577dd95f89844fd95f2b4141ba..0000000000000000000000000000000000000000 --- a/src/doc/development/benchmarking.md +++ /dev/null @@ -1,4 +0,0 @@ -# Benchmarking CaosDB # - -Please refer to the file `doc/devel/Benchmarking.md` in the CaosDB sources for developer resources -how to do benchmarking and profiling of CaosDB. diff --git a/src/doc/development/benchmarking.rst b/src/doc/development/benchmarking.rst new file mode 100644 index 0000000000000000000000000000000000000000..60d36d125b3749cff3538f837c8bd4f299233602 --- /dev/null +++ b/src/doc/development/benchmarking.rst @@ -0,0 +1,422 @@ +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 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: + +.. code:: cfg + + 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 in the folder +``benchmarking``: + +Python Script ``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. + +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). + +You might consider to increase +``performance_schema_events_transactions_history_long_size``. + +:: + + performance_schema_events_transactions_history_long_size=1000000 + +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 + +.. code:: 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. + +.. code:: 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: + +.. code:: 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.: + +.. code:: 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 + +.. code:: 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: + +.. code:: 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``). + +- Single timings can be added to instances of that class via the + ``addBenchmark(object, time)`` method. Multiple benchmarks for the + same object (typically just strings) can be averaged. +- Benchmarks can be serialized into XML, ``Container`` and ``Query`` + objects already use this with their included benchmarks to output + benchmarking results. +- To work with the benchmarks of often used objects, use these methods: + + - ``Container.getTransactionBenchmark().addBenchmark()`` + - ``Query.addBenchmark()`` + +To enable transaction benchmarks and disable caching in the server, set +these server settings: + +.. code:: cfg + + 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`` | transac | transaction/ | +| | tion/Transaction.java#135 | Retrieve.java#48 | ++----------------------+---------------------------+------------------+ +| ``Re | transac | transaction/R | +| trieve.transaction`` | tion/Transaction.java#174 | etrieve.java#133 | ++----------------------+---------------------------+------------------+ +| ``Retriev | transac | transaction/ | +| e.post_transaction`` | tion/Transaction.java#182 | Retrieve.java#77 | ++----------------------+---------------------------+------------------+ +| ``EntityResource.h | resource/transactio | all except XML | +| ttpGetInChildClass`` | n/EntityResource.java#118 | 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 + +.. code:: 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: + +.. code:: 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: + +.. code:: 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 +---------------------------------------------- + +- ``Transaction.execute()`` :: Logs benchmarks for events like: + + - ``INIT`` :: The transaction’s ``init()`` method. + - ``PRE_CHECK`` + - ``CHECK`` + - ``POST_CHECK`` + - ``PRE_TRANSACTION`` + - ``TRANSACTION`` -> typically calls + ``database.backend.transaction.[BackendTransaction].execute()``, + which in turn calls, some levels deeper, + ``backend.transaction.....execute(<k extends BackendTransaction> t)`` + -> see next point + - … + +- ``backend.transaction.[...].execute(transaction)`` :: This method is + benchmarked again (via parent class ``BackendTransaction``), this is + probably the deepest level of benchmarking currently (Benchmark is + logged as e.g. ``<RetrieveFullEntity>...</>``). It finally calls + ``[MySQLTransaction].execute()``. +- ``[MySQLTransaction].execute()`` :: This is the deepest backend + implementation part, it typically creates a prepared statement and + executes it. +- Currently not benchmarked separately: + + - Getting the actual implementation (probably fast?) + - Preparing the SQL statement + - Executing the SQL statement + - Java-side caching + +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/development/devel.rst b/src/doc/development/devel.rst index 141b045594fe6f8a58c86f758734e160e5a8c2fe..cf0ba37e75c967b755bc2951e803110b15f23baf 100644 --- a/src/doc/development/devel.rst +++ b/src/doc/development/devel.rst @@ -6,6 +6,8 @@ Developing CaosDB :maxdepth: 2 Structure of the Java code <structure> + Testing the server code <testing> + Logging server output <logging> Benchmarking CaosDB <benchmarking> CaosDB is an Open-Source project, so anyone may modify the source as they like. These pages aim to diff --git a/src/doc/development/logging.rst b/src/doc/development/logging.rst new file mode 100644 index 0000000000000000000000000000000000000000..1b0a94935a0c701fd9d8ba28a604f11b357d1a89 --- /dev/null +++ b/src/doc/development/logging.rst @@ -0,0 +1,126 @@ +Logging +======= + +Framework +--------- + +We use the SLF4J API with a log4j2 backend for all of our Code. Please +do not use log4j2 directly or any other logging API. + +Note that some libraries on the classpath use the ``java.util.logging`` +API and log4j1 logging framework instead. These loggers cannot be +configurated with the help of this README by now. + +Configuration +------------- + +The configuration of the log4j2 backend is done via ``properties`` files which comply with the +`log4j2 specifications +<https://logging.apache.org/log4j/2.x/manual/configuration.html#Properties>`__. +XML, YAML, or JSON files are not supported. The usual mechanisms for +automatic configuration with such files is disabled. Instead, files have +to be placed into the ``conf`` subdirs, as follows: + +Default and Debug Logging +~~~~~~~~~~~~~~~~~~~~~~~~~ + +The default configuration is located at +``conf/core/log4j2-default.properties``. For the debug mode, the +configuration from ``conf/core/log4j2-debug.properties`` is merged with +the default configuration. These files should not be changed by the +user. + +User Defined Logging +~~~~~~~~~~~~~~~~~~~~ + +The default and debug configuration can be overridden by the user with +``conf/ext/log4j2.properties`` and any file in the directory +``conf/ext/log4j2.properties.d/`` which is suffixed by ``.properties``. +All logging configuration files are merged using the standard merge +strategy of log4: + + .. rubric:: Composite Configuration + :name: composite-configuration + + Log4j allows multiple configuration files to be used by specifying + them as a list of comma separated file paths on + log4j.configurationFile. The merge logic can be controlled by + specifying a class that implements the MergeStrategy interface on the + log4j.mergeStrategy property. The default merge strategy will merge + the files using the following rules: + + 1. The global configuration attributes are aggregated with those in later configurations + replacing those in previous configurations, with the exception that the highest status level + and the lowest monitorInterval greater than 0 will be used. + + 2. Properties from all configurations are aggregated. Duplicate properties replace those in + previous configurations. + + 3. Filters are aggregated under a CompositeFilter if more than one Filter is defined. Since + Filters are not named duplicates may be present. + + 4. Scripts and ScriptFile references are aggregated. Duplicate definiations replace those in + previous configurations. + + 5. Appenders are aggregated. Appenders with the same name are replaced by those in later + configurations, including all of the Appender’s subcomponents. + + 6. Loggers are all aggregated. Logger attributes are individually merged with duplicates being + replaced by those in later configurations. Appender references on a Logger are aggregated with + duplicates being replaced by those in later configurations. Filters on a Logger are aggregated + under a CompositeFilter if more than one Filter is defined. Since Filters are not named + duplicates may be present. Filters under Appender references included or discarded depending + on whether their parent Appender reference is kept or discarded. + +`Source <https://logging.apache.org/log4j/2.x/manual/configuration.html#CompositeConfiguration>`__ + +Some Details and Examples +------------------------- + +Make Verbose +~~~~~~~~~~~~ + +To make the server logs on the console more verbose, insert +``rootLogger.level = DEBUG`` or even ``rootLogger.level = TRACE`` into a +properties file in the ``conf/ext/log4j2.properties.d/`` directory or +the ``conf/ext/log4j2.properties`` file. + +Log Directory +~~~~~~~~~~~~~ + +By default, log files go to ``./log/``, +e.g. ``./log/request_errors/current.log``. The log directory in +``DEBUG_MODE`` is located at ``./testlog/``. + +To change that, insert ``property.LOG_DIR = /path/to/my/logs`` into a +properties file in the ``conf/ext/log4j2.properties.d/`` directory or +the ``conf/ext/log4j2.properties`` file + +Special loggers +~~~~~~~~~~~~~~~ + +- ``REQUEST_ERRORS_LOGGER`` for logging server errors with SRID, full + request and full response. WARNING: This logger stores unencrypted + content of request with possibly confidential content. +- ``REQUEST_TIME_LOGGER`` for timing the requests. + +These loggers are defined in the ``conf/core/log4j2-default.properties`` +file. + +Enable Request Time Logger +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The ``REQUEST_TIME_LOGGER`` is disabled by default, its log level is set +to ``OFF``. To enable it and write logs to the directory denoted by +``property.LOG_DIR``, create a ``properties`` file under +``conf/ext/log4j2.properties.d/`` which contains at least + +.. code:: properties + + property.REQUEST_TIME_LOGGER_LEVEL = TRACE + +debug.log +~~~~~~~~~ + +When in ``DEBUG_MODE``, e.g. when started with ``make run-debug``, the +server also writes all logs to ``debug.log`` in the log directory. diff --git a/src/doc/development/testing.rst b/src/doc/development/testing.rst new file mode 100644 index 0000000000000000000000000000000000000000..2587695d67684d132e942b8eb4f25cd1038c9907 --- /dev/null +++ b/src/doc/development/testing.rst @@ -0,0 +1,22 @@ +Testing the server code +----------------------- + +Whether developing new features, refacturing code or fixing bugs, the server +code should be thoroughly tested for correct and incorrect behvaiour, on correct +and incorrect input. + +Writing tests +~~~~~~~~~~~~~ + +Tests go into ``src/test/java/caosdb/``, the files there can serve as examples for +writing tests. + +Running tests with Maven +~~~~~~~~~~~~~~~~~~~~~~~~ + +- Automatic testing can be done with ``make test`` or, after compilation, ``mvn test``. +- Tests of single modules can be started with ``mvn test -Dtest=TestClass``. +- Test of a single method ``footest``: ``mvn test -Dtest=TestClass#footest`` + + + diff --git a/src/doc/index.rst b/src/doc/index.rst index 1a5e4134ef7f934ff5018cc8847603f1165ab16e..e34afd382f0c4a1a5520b94a4fc00e1c0a67427b 100644 --- a/src/doc/index.rst +++ b/src/doc/index.rst @@ -11,6 +11,7 @@ Welcome to caosdb-server's documentation! Getting started <README_SETUP> Concepts <concepts> tutorials + FAQ Query Language <CaosDB-Query-Language> administration Development <development/devel> diff --git a/src/doc/specification/AbstractProperty.md b/src/doc/specification/AbstractProperty.md index 3a2fe7583480c7467bccb0a2bf94850ea30d7be4..062ef205e048aef4142c683c9c4c1820fd89fe36 100644 --- a/src/doc/specification/AbstractProperty.md +++ b/src/doc/specification/AbstractProperty.md @@ -1,11 +1,12 @@ -# Note # +# AbstractProperty Specification -> This document has not been updated for a long time. Although it is concerned with the mostly -> stable API, its content may no longer reflect the actual CaosDB behavior. +**Warning:** This specification is outdated. It is included to serve as a starting point for a more +up-to-date description of the `Property` entity. -# AbstractProperty Specification +## Note ## -**Warning:** This specification is outdated. It is included to serve as a starting point for a more up-to-date description of the `Property` entity. +> This document has not been updated for a long time. Although it is concerned with the mostly +> stable API, its content may no longer reflect the actual CaosDB behavior. ## Introduction An `AbstractProperty` is one of the basal objects of CaosDB. diff --git a/src/doc/specification/Authentication.rst b/src/doc/specification/Authentication.rst index 93d68c20171e55dad663ece719a78008793a4191..3fcd25dad0d7fb9e591e1d4a2d845b3d353fff8b 100644 --- a/src/doc/specification/Authentication.rst +++ b/src/doc/specification/Authentication.rst @@ -1,98 +1,48 @@ +============== Authentication ============== -Some features of CaosDB are available to registered users only. Making any -changes to the data stock via HTTP requires authentication by ``username`` **plus** -``password``. They are to be send as a HTTP header, while the password is to be -hashed by the sha512 algorithm: - -============= ====================== -username: password: -============= ====================== -``$username`` ``$SHA512ed_password`` -============= ====================== +Some features of CaosDB are available to registered users only. Making any changes to the data stock +via HTTP requires authentication. Sessions --------- +======== Login -^^^^^ - -Request Challenge -^^^^^^^^^^^^^^^^^ - -* ``GET http://host:port/mpidsserver/login?username=$username`` -* ``GET http://host:port/mpidsserver/login`` with ``username`` header - -**No password is required to be sent over http.** - -The request returns an AuthToken with a login challenge as a cookie. -The AuthToken is a dictionary of the following form: - -.. code-block:: - - {scope=$scope; - mode=LOGIN; - offerer=$offerer; - auth=$auth - expires=$expires; - date=$date; - hash=$hash; - session=$session; - } - -where - -* ``$scope`` :: A uri pattern string. Example: ``{ **/* }`` -* ``$mode`` :: ``ONETIME``, ``SESSION``, or ``LOGIN`` -* ``$offerer`` :: A valid username -* ``$auth`` :: A valid username -* ``$expires`` :: A ``YYYY-MM-DD HH:mm:ss[.nnnn]`` date string -* ``$date`` :: A ``YYYY-MM-DD HH:mm:ss[.nnnn]`` date string -* ``$hash`` :: A string -* ``$session`` :: A string - -The challenge is solved by concatenating the ``$hash`` string and -the user's ``$password`` string and calculating the sha512 hash of both. -Pseudo code: - -.. code-block:: +----- - $solution = sha512($hash + sha512($password)) +Authentication is done by ``username`` and ``password``. They must be sent as form data with a POST +request to the `/login/` resource: -Send Solution -^^^^^^^^^^^^^ +username: + The user name, for example ``admin`` (on demo.indiscale.com). -The old ``$hash`` string in the cookie has to be replaces by ``$solution`` and - the cookie is to be send with the next request: +password: + The password, for example ``caosdb`` (on demo.indiscale.com). -``PUT http://host:port/mpidsserver/login`` - -The server will return the user's entity in the HTTP body, e.g. +Logout +------ -.. code-block:: +The server does not invalidate AuthTokens. They invalidate after they expire or +when the server is being restartet. Client should just delete their AuthToken +to 'logout'. - <Response ...> - <User name="$username" ...> - ... - </User> - </Response> +However, in order to remove the AuthToken cookie from the browsers there is a +convenient resource which will invalidate the cookie (not the AuthToken). -and a new AuthToken with ``$mode=SESSION`` and a new expiration date and so -on. This AuthToken cookie is to be send with every request. +Send -Logout -^^^^^^ +``GET http://host:port/logout`` -Send +and the server will return an empty AuthToken cookie which immediately expires. -``PUT http://host:port/mpidsserver/logout`` +Example using ``curl`` +---------------------- -with a valid AuthToken cookie. No new AuthToken will be returned and no -AuthToken with that ``$session`` will be accepted anymore. +.. _curl-login: -Commandline solution with ``curl`` -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Login +~~~~~ To use curl for talking with the server, first save your password into a variable: ``PW=$(cat)`` @@ -102,10 +52,22 @@ password visible for a short time to everyone on your system: .. code-block:: sh - curl -X POST -c cookie.txt -D head.txt -H "Content-Type: application/x-www-form-urlencoded" -d username=<USERNAME> -d password="$PW" --insecure "https://<SERVER>/login + curl -X POST -c cookie.txt -D head.txt -d username=<USERNAME> -d password="$PW" --insecure "https://<SERVER>/login + +Now ``cookie.txt`` contains the required authentication token information in the ``SessionToken`` +cookie (url-encoded json). + +.. rubric:: Example token content + +.. code-block:: json + + ["S","PAM","admin",[],[],1682509668825,3600000,"Z6J4B[...]-OQ","31d3a[...]ab2c10"] + +Using the token +~~~~~~~~~~~~~~~ To use the cookie, pass it on with later requests: .. code-block:: sh - curl -X GET -b cookie.txt --insecure "https://<SERVER>/Entity/12345" + curl -X GET -b cookie.txt --insecure "https://<SERVER>/Entity/123" diff --git a/src/doc/specification/Datatype.md b/src/doc/specification/Datatype.md index 6a169042dce2be2e6dc939d0935f3336de264308..6354d6f2cfdb5215d94836bfe263e2d013bd71a8 100644 --- a/src/doc/specification/Datatype.md +++ b/src/doc/specification/Datatype.md @@ -73,7 +73,7 @@ Please file a new feature request as soon as you need them. ---- ## REFERENCE -* Description: REFERENCE values store the [Valid ID](../Glossary#valid-id) of an existing entity. The are useful to establish links between two entities. +* Description: REFERENCE values store the [Valid ID](../Glossary.html#valid-id) of an existing entity. The are useful to establish links between two entities. * Accepted Values: Any [Valid ID](./Glossary#valid-id) or [Valid Unique Existing Name](./Glossary#valid-unique-existing-name) or [Valid Unique Temporary ID](./Glossary#valid-unique-temporary-id) or [Valid Unique Prospective Name](./Glossary#valid-unique-prospective-pame). * Note: * After beeing processed successfully by the server the REFERENCE value is normalized to a [Valid ID](./Glossary#valid-id). I.e. it is guaranteed that a REFERENCE value of a valid property is a positive integer. diff --git a/src/doc/specification/Specification-of-the-Message-API.md b/src/doc/specification/Message-API.md similarity index 99% rename from src/doc/specification/Specification-of-the-Message-API.md rename to src/doc/specification/Message-API.md index fc08b343f67ca8c052395cb0979b02ab455e98fa..7a5d137bd277d9dea69df9d90ce76869d1d404c1 100644 --- a/src/doc/specification/Specification-of-the-Message-API.md +++ b/src/doc/specification/Message-API.md @@ -1,4 +1,4 @@ -# Specification of the Message API +# Message API ## Introduction API Version 0.1.0 diff --git a/src/doc/specification/Paging.md b/src/doc/specification/Paging.md index fb994639204704618af2b1c7a8ccb32301013af3..229e9c3bc254de5dcbe4455f4fc59b7538f811de 100644 --- a/src/doc/specification/Paging.md +++ b/src/doc/specification/Paging.md @@ -12,14 +12,14 @@ The Paging flag splits the retrieval of a (possibly huge) number entities into p ## Semantics -The `index` (starting with zero) denotes the index of the first entity to be retrieved. The `length` is the number of entities on that page. If `length` is omitted, the default number of entities is returned (as configured by a server contant called ...). If only the `name` is given the paging behaves as if the `index` has been zero. +The `index` (starting with zero) denotes the index of the first entity to be retrieved. The `length` is the number of entities on that page. If `length` is omitted, the default number of entities is returned (as configured by a server constant called ...). If only the `name` is given the paging behaves as if the `index` has been zero. ## Examples -`http://localhost:8123/mpidsserver/Entities/all?flags=P:24L50` returns 50 entities starting with the 25th entity which would be retrieved without paging. +`http://localhost:10080/Entities/all?flags=P:24L50` returns 50 entities starting with the 25th entity which would be retrieved without paging. -`http://localhost:8123/mpidsserver/Entities/all?flags=P:24` returns the default number of entities starting with the 25th entity which would be retrieved without paging. +`http://localhost:10080/Entities/all?flags=P:24` returns the default number of entities starting with the 25th entity which would be retrieved without paging. -`http://localhost:8123/mpidsserver/Entities/all?flags=P:L50` returns 50 entities starting with the first entity which would be retrieved without paging. +`http://localhost:10080/Entities/all?flags=P:L50` returns 50 entities starting with the first entity which would be retrieved without paging. -`http://localhost:8123/mpidsserver/Entities/all?flags=P` returns the default number of entities starting with the first entity which would be retrieved without paging. +`http://localhost:10080/Entities/all?flags=P` returns the default number of entities starting with the first entity which would be retrieved without paging. diff --git a/src/doc/specification/index.rst b/src/doc/specification/index.rst index 69677277fc793d513aa21f506295567340d462e1..51b0e070d5198603877ed336b353177b03a6e9f8 100644 --- a/src/doc/specification/index.rst +++ b/src/doc/specification/index.rst @@ -1,10 +1,11 @@ -Specification -============= +Specifications +============== + +Specifications of assorted topics .. toctree:: - :maxdepth: 2 + :maxdepth: 1 :caption: Contents: - :hidden: AbstractProperty Fileserver @@ -13,6 +14,7 @@ Specification Datatype Paging RecordType + Query syntax <../query-syntax> Server side scripting <Server-side-scripting> - Specification of the Message API <Specification-of-the-Message-API> - Specification of the Entity API <entity_api> + Message API <Message-API> + Entity API <entity_api> diff --git a/src/doc/tutorials/data_model_tutorial.rst b/src/doc/tutorials/data_model_tutorial.rst new file mode 100644 index 0000000000000000000000000000000000000000..c1de9b08a38623751ec3d50a38ee001fbe8619d9 --- /dev/null +++ b/src/doc/tutorials/data_model_tutorial.rst @@ -0,0 +1,215 @@ +Data model tutorial +=================== + +Data models are a central concept of research data management. The +open-source framework LinkAhead makes it easy to create an `agile +data model <https://docs.indiscale.com/caosdb-server/Data-Model.html>`__ +which suits your needs, so you can profit from your collected data fast. + +.. contents:: + :local: + +Frequently asked questions about data models +-------------------------------------------- + +What is a data model? +~~~~~~~~~~~~~~~~~~~~~ + +In LinkAhead, the `data model <https://docs.indiscale.com/caosdb-server/Data-Model.html>`__ +describes in a generic way how the data is structured. This document will help you understand this +concept and how you can adjust the data model to your needs. + +Let’s give one example what we mean by the data model describing the +data structure: In experimental research, the data model may say that +each experiment has one or more experimenters and also some lab notes. + +.. figure:: img/very_simple_data_model.png + :alt: The simplest data model consists of just one RecordType + + A very simple data model, consisting of only one RecordType + +The data model may also say that there are cell culture experiments, +which *inherit* all properties of generic experiments, but in addition +have the number of dishes, the time the cell cultures were started and a +*reference* to the cell line that was used. When we talk about +references, we mean that there are other data sets (in this case cell +lines) which are not embedded directly, but only pointed at. For +example, a number of cell culture experiments may reference the same +cell line, and when someone adds a note to that cell line, this note +will be available to all cell culture experiments. + +.. figure:: img/simple_data_model.png + :alt: The first data model extended by inheritance and references + + A simple data model, with four RecordTypes + +In LinkAhead, the data model consists of *RecordTypes* and *Properties*. Each `*RecordType* +<https://docs.indiscale.com/caosdb-server/Data-Model.html#record-types>`__ corresponds to one type +of data set, for example an experiment, a cell culture experiment or a cell line. And each data set +can have `*Properties* <https://docs.indiscale.com/caosdb-server/Data-Model.html#properties>`__ +which define it further: for example +the number of dishes in a cell culture experiment, a publication +describing a cell line or the cell line which was used for an +experiment. + +And what about the data? +~~~~~~~~~~~~~~~~~~~~~~~~ + +The data model describes the generic structure of the data, but what +does this mean for your actual data? When you want to store data in +LinkAhead, you typically look for a matching *RecordType* and +then create *Record*\ s of that type. This means for example, that a +*Record* of type “Cell Culture†should have the *Properties* that the +*RecordType* provides: experimenters, lab notes, number of dishes, time +of experiment start and the used cell lines. + +.. figure:: img/data_model_and_data.png + :alt: Records are always based upon (at least) one RecordType, this defines the relationship between data model and data + + A diagram showing the relationship between RecordTypes (the data + model) and Records (the data) + +Following a data model greatly simplifies your scientific research data +management, and this is what LinkAhead is for: You know that all +(or most) of your cell culture experiments will have the same set of +properties, so you can search for all experiments where the properties +match your desired criteria, for example those with cell lines which +were described in publications of a certain author. + +What if I need to change my data model? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Science moves fast, and your infrastructure should be able to follow +just as swiftly. The data model in LinkAhead was designed to be +very flexible, so it can adapt to your needs. + +There are multiple ways to modify and enhance your data model, for +example you could use the web application or program the changes with +the Python client. This document describes how to change the data model +with the LinkAhead web application. + +What happens to my data if the data model changes? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +With most systems, changing the data model is not possible without +migrating the existing data. With LinkAhead however, your +“legacy†data can simply stay where it is, it will not be modified. New +data, that you enter after changing the data model, will of course +follow your changes and adhere to your “new†data model. + +Modifying the data model in LinkAhead +------------------------------------- + +The easiest way to change the data model is to `use the LinkAhead +web +application <https://docs.indiscale.com/caosdb-webui/tutorials/edit_mode.html>`__. +But the first step is to look at the current data model (*RecordTypes* +and *Properties*) and your current situation, to find out what you need +changed. It may help to ask yourself these questions: + +- Do you want to describe data of existing types in more detail? -> :ref:`Create + *Properties*<new_property>` if necessary, then `add + <https://docs.indiscale.com/caosdb-webui/tutorials/edit_mode.html#add-properties-to-an-existing-recordtype>`__ + these *Properties* to the existing *RecordTypes*. +- Do you need to add a property more than once? -> Make that property `a list property + <https://docs.indiscale.com/caosdb-webui/tutorials/edit_mode.html#make-a-property-into-a-list>`__\ + . +- Are there completely new types of data? -> :ref:`Create a new + *RecordType* <new_recordtype>` . +- Are there new types of data which would nearly fit into an existing + RecordType, but not quite? Do you still want to keep the existing + RecordType for some purposes, but require changes or additions for + other purposes? -> :ref:`Create a new + *RecordType* <new_recordtype>` which inherits from the + existing RecordType as a parent. +- Do you have a new setup, new devices, new protocols, new software? -> + :ref:`Create a new + *RecordType* <new_recordtype>` . +- Are there *Properties* or *RecordTypes* which were created in the past, but will never be used? -> + `Delete them + <https://docs.indiscale.com/caosdb-webui/tutorials/edit_mode.html#delete-an-entity>`__. + +Once you know what you want to do, open the LinkAhead web application, log in and click on the “Edit +Mode†button in the menu. Then follow one of the instructions in the next sections or the general `Edit +Mode documentation <https://docs.indiscale.com/caosdb-webui/tutorials/edit_mode.html>`__.. + +.. figure:: img/edit_mode_button.png + :alt: The Edit Mode button appears when a user is logged in. + + A screen shot of the menu, with the Edit Mode button highlighted + +.. _new_property: + +Create a new Property +~~~~~~~~~~~~~~~~~~~~~ + +Creating the required Properties often is the first step for changes to +the data model. + +1. Check that there is no Property yet which does what you want: + + - Search for existing Properties: ``FIND PROPERTY *qualit*`` to + search for Properties with “qualit†in the name, ``FIND PROPERTY`` + for all Properties. + - Look at related RecordTypes, maybe they already use a Property + which does the thing you need. + - Ask around. If possible at least two persons. Your team mates may + have had the same thoughts already, or may know about someone who + implemented your desired Property. + +2. Activate the *Edit Mode* in the top menu. Now the edit mode toolbox + on the right hand side of the screen should be visible. +3. Click on the “Create Property†button in the toolbox. A dialog for + the new Property shows up. +4. Follow the `WebUI documentation + <https://docs.indiscale.com/caosdb-webui/tutorials/edit_mode.html#new-property>`__ for Property + creation. + +.. _new_recordtype: + +Create a new RecordType +~~~~~~~~~~~~~~~~~~~~~~~ + +1. Check that there is no RecordType yet which does what you want: + + - Search for existing RecordTypes: ``FIND RECORDTYPE *experi*`` to + search for RecordTypes with “experi†in the name, + ``FIND RECORDTYPE`` for all RecordTypes. + - Look at related RecordTypes, maybe you can inherit from one? + - Ask around. If possible at least two persons. You team mates may + have had the same thoughts already, or may know about someone who + implemented your desired RecordType. + +2. Activate the *Edit Mode* in the top menu. Now the edit mode toolbox + on the right hand side of the screen should be visible. + +3. Click on the “Create RecordType†button in the toolbox. A dialog for + the new Property shows up. + +4. Follow the `WebUI documentation + <https://docs.indiscale.com/caosdb-webui/tutorials/edit_mode.html#new-recordtype>`__ for + RecordType creation. + +What about letting users only choose from a fixed set of values (a.k.a. enums)? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You may ask yourself how you can restrict users (or yourself) to choose +property values from a fixed set. For example, entering the +experimenters in an “Experiment†record may be cumbersome and +error-prone. Or entering a chemical formula again and again may become +tedious. This is where data management tools like LinkAhead can +help you by referencing between entities. + +In this example, instead of having a TEXT Property “experimenterâ€, where +users need to enter the name by hand, you could create a new RecordType +“Personâ€. Then create a Record for each experimenter in your team. And +finally, remove the “experimenter†TEXT Property and create a new +REFERENCE Property which references to the “Person†records. Now all the +users have to do is to choose the experimenter from a drop-down menu in +LinkAhead. + +.. figure:: img/screenshot_dropdown.png + :alt: Users may choose one of multiple Persons when enums are implemented via REFERENCE Properties + + A screenshot of a drop down menu where one of multiple “Person†records may be chosen + diff --git a/src/doc/tutorials/img/data_model_and_data.png b/src/doc/tutorials/img/data_model_and_data.png new file mode 100644 index 0000000000000000000000000000000000000000..f7b0d3b8c72bf5ff36a74dc50ef386977ddb2512 Binary files /dev/null and b/src/doc/tutorials/img/data_model_and_data.png differ diff --git a/src/doc/tutorials/img/data_model_and_data.svg b/src/doc/tutorials/img/data_model_and_data.svg new file mode 100644 index 0000000000000000000000000000000000000000..5f6692c541e20d93513f0ef9b759506f35797185 --- /dev/null +++ b/src/doc/tutorials/img/data_model_and_data.svg @@ -0,0 +1,2071 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="123.13786mm" + height="55.646351mm" + viewBox="0 0 479.9487 216.88946" + version="1.2" + id="svg236" + sodipodi:docname="data_model_and_data.svg" + inkscape:version="1.0.2 (e86c870879, 2021-01-15)" + inkscape:export-filename="/home/daniel/indiscale/management/external/bmpg/management/documentation/data_model/how_to_datamodel/data_model_and_data.png" + inkscape:export-xdpi="412.54572" + inkscape:export-ydpi="412.54572"> + <metadata + id="metadata240"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title>Qt SVG Document</dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1920" + inkscape:window-height="1135" + id="namedview238" + showgrid="false" + inkscape:zoom="2.6127896" + inkscape:cx="232.70147" + inkscape:cy="105.15846" + inkscape:window-x="0" + inkscape:window-y="0" + inkscape:window-maximized="1" + inkscape:current-layer="layer1" + inkscape:document-rotation="0" /> + <title + id="title2">Qt SVG Document</title> + <desc + id="desc4">Generated with Qt</desc> + <defs + id="defs6"> + <marker + style="overflow:visible" + id="marker3403" + refX="0" + refY="0" + orient="auto" + inkscape:stockid="Arrow2Lend" + inkscape:isstock="true"> + <path + transform="matrix(-1.1,0,0,-1.1,-1.1,0)" + d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z" + style="fill:#0000ff;fill-opacity:1;fill-rule:evenodd;stroke:#0000ff;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1" + id="path3401" /> + </marker> + <marker + style="overflow:visible" + id="marker1523" + refX="0" + refY="0" + orient="auto" + inkscape:stockid="Arrow2Lend" + inkscape:isstock="true"> + <path + transform="matrix(-1.1,0,0,-1.1,-1.1,0)" + d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z" + style="fill:#0000ff;fill-opacity:1;fill-rule:evenodd;stroke:#0000ff;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1" + id="path1521" /> + </marker> + <marker + style="overflow:visible" + id="marker1333" + refX="0" + refY="0" + orient="auto" + inkscape:stockid="Arrow2Mend" + inkscape:isstock="true"> + <path + transform="scale(-0.6)" + d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z" + style="fill:#0000ff;fill-opacity:1;fill-rule:evenodd;stroke:#0000ff;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1" + id="path1331" /> + </marker> + <marker + style="overflow:visible" + id="Arrow1Mend" + refX="0" + refY="0" + orient="auto" + inkscape:stockid="Arrow1Mend" + inkscape:isstock="true"> + <path + transform="matrix(-0.4,0,0,-0.4,-4,0)" + style="fill:#0000ff;fill-opacity:1;fill-rule:evenodd;stroke:#0000ff;stroke-width:1pt;stroke-opacity:1" + d="M 0,0 5,-5 -12.5,0 5,5 Z" + id="path976" /> + </marker> + <marker + style="overflow:visible" + id="marker1523-5" + refX="0" + refY="0" + orient="auto" + inkscape:stockid="Arrow2Lend" + inkscape:isstock="true" + inkscape:collect="always"> + <path + transform="matrix(-1.1,0,0,-1.1,-1.1,0)" + d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z" + style="fill:#0000ff;fill-opacity:1;fill-rule:evenodd;stroke:#0000ff;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1" + id="path1521-1" /> + </marker> + </defs> + <g + inkscape:groupmode="layer" + id="layer1" + inkscape:label="Layer 1" + transform="translate(-0.37325826,-0.37362263)"> + <g + fill="none" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g8" + style="fill-rule:evenodd" /> + <g + fill="none" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g10" + style="fill-rule:evenodd" /> + <g + fill="none" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,158.491,453.661)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g12" + style="fill-rule:evenodd" /> + <g + fill="none" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,158.491,453.661)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g18" + style="fill-rule:evenodd" /> + <g + fill="none" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,0.992009,93.5484)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g20" + style="fill-rule:evenodd" /> + <g + fill="#ffffc0" + fill-opacity="1" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,0.992009,93.5484)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="700" + font-style="normal" + id="g26" + style="fill-rule:evenodd" /> + <g + fill="#ffffc0" + fill-opacity="1" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,0.992009,93.5484)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g36" + style="fill-rule:evenodd" /> + <g + fill="#ffffc0" + fill-opacity="1" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,0.992009,93.5484)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g38" + style="fill-rule:evenodd" /> + <g + fill="#ffffc0" + fill-opacity="1" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,0.992009,93.5484)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g44" + style="fill-rule:evenodd" /> + <g + fill="#ffffc0" + fill-opacity="1" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,0.992009,93.5484)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g50" + style="fill-rule:evenodd" /> + <g + fill="#ffffc0" + fill-opacity="1" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,0.992009,93.5484)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g56" + style="fill-rule:evenodd" /> + <g + fill="#ffffc0" + fill-opacity="1" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,0.992009,93.5484)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g62" + style="fill-rule:evenodd" /> + <g + fill="#ffffc0" + fill-opacity="1" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,0.992009,93.5484)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g64" + style="fill-rule:evenodd" /> + <g + fill="#ffffc0" + fill-opacity="1" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g66" + style="fill-rule:evenodd" /> + <g + fill="none" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,0.992009,93.5484)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g68" + style="fill-rule:evenodd" /> + <g + fill="none" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,158.491,453.661)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g70" + style="fill-rule:evenodd" /> + <g + fill="none" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,158.491,453.661)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g72" + style="fill-rule:evenodd" /> + <g + fill="none" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(-0.992009,0,0,-0.992009,90.3118,64.4806)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g74" + style="fill-rule:evenodd" /> + <g + fill="none" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(-0.992009,0,0,-0.992009,90.3118,64.4806)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g80" + style="fill-rule:evenodd" /> + <g + fill="none" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,158.491,453.661)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g82" + style="fill-rule:evenodd" /> + <g + id="g3044"> + <g + fill="#ffffff" + fill-opacity="1" + stroke="#ff0000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="round" + stroke-linejoin="round" + transform="matrix(-0.992009,0,0,-0.992009,90.3118,64.4806)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g78" + style="fill-rule:evenodd;stroke-width:1.24747;stroke-miterlimit:4;stroke-dasharray:none"> + <path + vector-effect="" + fill-rule="evenodd" + d="M -6,0 0,10 6,0 H -6" + id="path76" + style="stroke:#ff0000;stroke-width:1.24747;stroke-miterlimit:4;stroke-dasharray:none" /> + </g> + <g + fill="none" + stroke="#ff0000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="round" + stroke-linejoin="round" + transform="matrix(0.992009,0,0,0.992009,158.491,453.661)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g86" + style="fill-rule:evenodd;stroke-width:1.24747;stroke-miterlimit:4;stroke-dasharray:none"> + <path + vector-effect="" + fill-rule="evenodd" + d="m -68.7281,-363.013 v -29.302" + id="path84" + style="stroke:#ff0000;stroke-width:1.24747;stroke-miterlimit:4;stroke-dasharray:none" /> + </g> + </g> + <g + fill="none" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,158.491,453.661)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g88" + style="fill-rule:evenodd" /> + <g + fill="none" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,158.491,453.661)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g90" + style="fill-rule:evenodd" /> + <g + fill="none" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,158.491,453.661)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g92" + style="fill-rule:evenodd" /> + <g + fill="none" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0,0.992009,-0.992009,0,197.41,178.126)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g94" + style="fill-rule:evenodd" /> + <g + fill="none" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0,0.992009,-0.992009,0,197.41,178.126)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g100" + style="fill-rule:evenodd" /> + <g + fill="none" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,158.491,453.661)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g102" + style="fill-rule:evenodd" /> + <g + fill="none" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,158.491,453.661)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g108" + style="fill-rule:evenodd" /> + <g + fill="none" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,158.491,453.661)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g110" + style="fill-rule:evenodd" /> + <g + fill="none" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,158.491,453.661)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g112" + style="fill-rule:evenodd" /> + <g + fill="none" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(-0.992009,0,0,-0.992009,319.539,211.365)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g114" + style="fill-rule:evenodd" /> + <g + fill="none" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(-0.992009,0,0,-0.992009,319.539,211.365)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g120" + style="fill-rule:evenodd" /> + <g + fill="none" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,158.491,453.661)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g122" + style="fill-rule:evenodd" /> + <g + fill="none" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,158.491,453.661)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g128" + style="fill-rule:evenodd" /> + <g + fill="none" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,24.4333,0.992009)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g130" + style="fill-rule:evenodd" /> + <g + fill="#ffffc0" + fill-opacity="1" + stroke="#ff0000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,24.4333,0.992009)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g134" + style="fill-rule:evenodd;stroke-width:1.24747;stroke-miterlimit:4;stroke-dasharray:none"> + <rect + vector-effect="" + x="0" + y="0" + width="135" + height="54" + id="rect132" + style="stroke:#ff0000;stroke-width:1.24747;stroke-miterlimit:4;stroke-dasharray:none" /> + </g> + <g + fill="#ffffc0" + fill-opacity="1" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,24.4333,0.992009)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="700" + font-style="normal" + id="g136" + style="fill-rule:evenodd" /> + <text + fill="#000000" + fill-opacity="1" + stroke="none" + xml:space="preserve" + x="54.689575" + y="14.38413" + font-family="'Noto Sans'" + font-size="12.2761px" + font-weight="700" + font-style="normal" + id="text138" + style="fill-rule:evenodd;stroke-width:0.992009;stroke-linecap:square;stroke-linejoin:bevel;stroke-opacity:1">Experiment</text> + <g + fill="#ffffc0" + fill-opacity="1" + stroke="#ff0000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,24.4333,0.992009)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g144" + style="fill-rule:evenodd;stroke-width:1.24747;stroke-miterlimit:4;stroke-dasharray:none"> + <polyline + fill="none" + vector-effect="" + points="0,18 135,18 " + id="polyline142" + style="stroke:#ff0000;stroke-width:1.24747;stroke-miterlimit:4;stroke-dasharray:none" /> + </g> + <g + fill="#ffffc0" + fill-opacity="1" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,24.4333,0.992009)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g146" + style="fill-rule:evenodd" /> + <g + fill="#ffffc0" + fill-opacity="1" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,24.4333,0.992009)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g148" + style="fill-rule:evenodd" /> + <text + fill="#000000" + fill-opacity="1" + stroke="none" + xml:space="preserve" + x="29.393345" + y="32.240292" + font-family="'Noto Sans'" + font-size="12.2761px" + font-weight="400" + font-style="normal" + id="text150" + style="fill-rule:evenodd;stroke-width:0.992009;stroke-linecap:square;stroke-linejoin:bevel;stroke-opacity:1">experimenter : TEXT</text> + <g + fill="#ffffc0" + fill-opacity="1" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,24.4333,0.992009)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g154" + style="fill-rule:evenodd" /> + <text + fill="#000000" + fill-opacity="1" + stroke="none" + xml:space="preserve" + x="29.393345" + y="50.096455" + font-family="'Noto Sans'" + font-size="12.2761px" + font-weight="400" + font-style="normal" + id="text156" + style="fill-rule:evenodd;stroke-width:0.992009;stroke-linecap:square;stroke-linejoin:bevel;stroke-opacity:1">lab_notes : FILE</text> + <g + fill="#ffffc0" + fill-opacity="1" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,24.4333,0.992009)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g160" + style="fill-rule:evenodd" /> + <g + fill="#ffffc0" + fill-opacity="1" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,24.4333,0.992009)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g162" + style="fill-rule:evenodd" /> + <g + fill="#ffffc0" + fill-opacity="1" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g164" + style="fill-rule:evenodd" /> + <g + fill="none" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,24.4333,0.992009)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g166" + style="fill-rule:evenodd" /> + <g + fill="none" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,243.928,247.296)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g168" + style="fill-rule:evenodd" /> + <g + fill="#ffffc0" + fill-opacity="1" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,243.928,247.296)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="700" + font-style="normal" + id="g174" + style="fill-rule:evenodd" /> + <g + fill="#ffffc0" + fill-opacity="1" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,243.928,247.296)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g184" + style="fill-rule:evenodd" /> + <g + fill="#ffffc0" + fill-opacity="1" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,243.928,247.296)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g186" + style="fill-rule:evenodd" /> + <g + fill="#ffffc0" + fill-opacity="1" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,243.928,247.296)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g192" + style="fill-rule:evenodd" /> + <g + fill="#ffffc0" + fill-opacity="1" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,243.928,247.296)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g194" + style="fill-rule:evenodd" /> + <g + fill="#ffffc0" + fill-opacity="1" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g196" + style="fill-rule:evenodd" /> + <g + fill="none" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,243.928,247.296)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g198" + style="fill-rule:evenodd" /> + <g + fill="none" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,288.811,165.733)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g200" + style="fill-rule:evenodd" /> + <g + fill="#ffffc0" + fill-opacity="1" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,288.811,165.733)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="700" + font-style="normal" + id="g206" + style="fill-rule:evenodd" /> + <g + fill="#ffffc0" + fill-opacity="1" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,288.811,165.733)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g216" + style="fill-rule:evenodd" /> + <g + fill="#ffffc0" + fill-opacity="1" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,288.811,165.733)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g218" + style="fill-rule:evenodd" /> + <g + fill="#ffffc0" + fill-opacity="1" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,288.811,165.733)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g224" + style="fill-rule:evenodd" /> + <g + fill="#ffffc0" + fill-opacity="1" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,288.811,165.733)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g226" + style="fill-rule:evenodd" /> + <g + fill="#ffffc0" + fill-opacity="1" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g228" + style="fill-rule:evenodd" /> + <g + fill="none" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,288.811,165.733)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g230" + style="fill-rule:evenodd" /> + <g + fill="none" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g232" + style="fill-rule:evenodd" /> + <g + id="g3098"> + <g + id="g3084"> + <rect + vector-effect="" + x="0.99200898" + y="93.548401" + width="186.4977" + height="89.280807" + id="rect22" + style="font-style:normal;font-weight:400;font-size:12.375px;font-family:'Noto Sans';fill:#ffffc0;fill-opacity:1;fill-rule:evenodd;stroke:#ff0000;stroke-width:1.2375;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <polyline + fill="none" + vector-effect="" + points="0,18 188,18 " + id="polyline32" + style="font-style:normal;font-weight:400;font-size:12.375px;font-family:'Noto Sans';fill-opacity:1;fill-rule:evenodd;stroke:#ff0000;stroke-width:1.24747;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + transform="matrix(0.992009,0,0,0.992009,0.992009,93.5484)" /> + </g> + <text + fill="#000000" + fill-opacity="1" + stroke="none" + xml:space="preserve" + x="31.640734" + y="106.94052" + font-family="'Noto Sans'" + font-size="12.2761px" + font-weight="700" + font-style="normal" + id="text28" + style="fill-rule:evenodd;stroke-width:0.992009;stroke-linecap:square;stroke-linejoin:bevel;stroke-opacity:1">CellCultureExperiment</text> + <text + fill="#000000" + fill-opacity="1" + stroke="none" + xml:space="preserve" + x="5.952054" + y="124.79668" + font-family="'Noto Sans'" + font-size="12.2761px" + font-weight="400" + font-style="normal" + id="text40" + style="fill-rule:evenodd;stroke-width:0.992009;stroke-linecap:square;stroke-linejoin:bevel;stroke-opacity:1">experimenter : TEXT</text> + <text + fill="#000000" + fill-opacity="1" + stroke="none" + xml:space="preserve" + x="5.952054" + y="142.65285" + font-family="'Noto Sans'" + font-size="12.2761px" + font-weight="400" + font-style="normal" + id="text46" + style="fill-rule:evenodd;stroke-width:0.992009;stroke-linecap:square;stroke-linejoin:bevel;stroke-opacity:1">lab_notes : FILE</text> + <text + fill="#000000" + fill-opacity="1" + stroke="none" + xml:space="preserve" + x="5.952054" + y="160.509" + font-family="'Noto Sans'" + font-size="12.2761px" + font-weight="400" + font-style="normal" + id="text52" + style="fill-rule:evenodd;stroke-width:0.992009;stroke-linecap:square;stroke-linejoin:bevel;stroke-opacity:1">number_of_dishes : INTEGER</text> + <text + fill="#000000" + fill-opacity="1" + stroke="none" + xml:space="preserve" + x="5.952054" + y="178.36517" + font-family="'Noto Sans'" + font-size="12.2761px" + font-weight="400" + font-style="normal" + id="text58" + style="fill-rule:evenodd;stroke-width:0.992009;stroke-linecap:square;stroke-linejoin:bevel;stroke-opacity:1">cell_line : CellLine</text> + <g + id="g1462" + transform="translate(-285.2144,-71.568939)"> + <rect + style="opacity:1;fill:#d7f4d7;stroke:#008000;stroke-width:0.839788;paint-order:markers fill stroke;stop-color:#000000" + id="rect1456" + width="18.926079" + height="13.833775" + x="289.03854" + y="167.14027" + ry="4.099587" /> + <text + fill="#000000" + fill-opacity="1" + stroke="none" + xml:space="preserve" + x="290.79922" + y="178.57553" + font-family="'Noto Sans'" + font-size="12.2761px" + font-weight="700" + font-style="normal" + id="text1460" + style="fill-rule:evenodd;stroke-width:0.992009"><tspan + style="fill:#008000" + id="tspan1458">RT</tspan></text> + </g> + </g> + <g + id="g1470" + transform="translate(-261.77311,-164.12532)"> + <rect + style="opacity:1;fill:#d7f4d7;stroke:#008000;stroke-width:0.839788;paint-order:markers fill stroke;stop-color:#000000" + id="rect1464" + width="18.926079" + height="13.833775" + x="289.03854" + y="167.14027" + ry="4.099587" /> + <text + fill="#000000" + fill-opacity="1" + stroke="none" + xml:space="preserve" + x="290.79922" + y="178.57553" + font-family="'Noto Sans'" + font-size="12.2761px" + font-weight="700" + font-style="normal" + id="text1468" + style="fill-rule:evenodd;stroke-width:0.992009"><tspan + style="fill:#008000" + id="tspan1466">RT</tspan></text> + </g> + <g + id="g2988"> + <g + id="g2964"> + <g + fill="#ffffc0" + fill-opacity="1" + stroke="#ff0000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,213.32298,2.1385369)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g2922" + style="fill:#d5f6ff;fill-rule:evenodd;stroke:#0044aa;stroke-width:1.24747;stroke-miterlimit:4;stroke-dasharray:none"> + <rect + vector-effect="" + x="0" + y="0" + width="135" + height="54" + id="rect2920" + style="fill:#d5f6ff;stroke:#0044aa;stroke-width:1.24747;stroke-miterlimit:4;stroke-dasharray:none" /> + </g> + <g + fill="#ffffc0" + fill-opacity="1" + stroke="#ff0000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,213.32298,2.1385369)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g2926" + style="fill-rule:evenodd;stroke:#0044aa;stroke-width:1.24747;stroke-miterlimit:4;stroke-dasharray:none"> + <polyline + fill="none" + vector-effect="" + points="0,18 135,18 " + id="polyline2924" + style="stroke:#0044aa;stroke-width:1.24747;stroke-miterlimit:4;stroke-dasharray:none" /> + </g> + </g> + <text + fill="#000000" + fill-opacity="1" + stroke="none" + xml:space="preserve" + x="236.03297" + y="14.17053" + font-family="'Noto Sans'" + font-size="12.2761px" + font-weight="400" + font-style="normal" + id="text150-3" + style="fill-rule:evenodd;stroke-width:0.992009;stroke-linecap:square;stroke-linejoin:bevel;stroke-opacity:1">experiment 1</text> + <text + fill="#000000" + fill-opacity="1" + stroke="none" + xml:space="preserve" + x="218.03311" + y="32.240292" + font-family="'Noto Sans'" + font-size="12.2761px" + font-weight="400" + font-style="normal" + id="text2956" + style="fill-rule:evenodd;stroke-width:0.992009;stroke-linecap:square;stroke-linejoin:bevel;stroke-opacity:1">experimenter : Sheila</text> + <text + fill="#000000" + fill-opacity="1" + stroke="none" + xml:space="preserve" + x="218.03311" + y="50.096455" + font-family="'Noto Sans'" + font-size="12.2761px" + font-weight="400" + font-style="normal" + id="text2958" + style="fill-rule:evenodd;stroke-width:0.992009;stroke-linecap:square;stroke-linejoin:bevel;stroke-opacity:1">lab_notes : /data/dat3.</text> + <g + id="g3122-2" + transform="translate(-73.372734,-162.59558)"> + <rect + style="opacity:1;fill:#ffd5d5;stroke:#ff0000;stroke-width:0.839788;paint-order:markers fill stroke;stop-color:#000000" + id="rect3116-2" + width="15.923747" + height="13.833771" + x="289.03854" + y="167.14027" + ry="6.9168854" /> + <text + fill="#000000" + fill-opacity="1" + stroke="none" + xml:space="preserve" + x="292.86172" + y="178.57553" + font-family="'Noto Sans'" + font-size="12.2761px" + font-weight="700" + font-style="normal" + id="text3120-3" + style="fill:#ff0000;fill-rule:evenodd;stroke-width:0.992009"><tspan + style="fill:#ff0000" + id="tspan3118-1">R</tspan></text> + </g> + </g> + <g + id="g3006" + transform="translate(14.001362,13.026627)"> + <g + id="g2998"> + <g + fill="#ffffc0" + fill-opacity="1" + stroke="#ff0000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,213.32298,2.1385369)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g2992" + style="fill:#d5f6ff;fill-rule:evenodd;stroke:#0044aa;stroke-width:1.24747;stroke-miterlimit:4;stroke-dasharray:none"> + <rect + vector-effect="" + x="0" + y="0" + width="135" + height="54" + id="rect2990" + style="fill:#d5f6ff;stroke:#0044aa;stroke-width:1.24747;stroke-miterlimit:4;stroke-dasharray:none" /> + </g> + <g + fill="#ffffc0" + fill-opacity="1" + stroke="#ff0000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,213.32298,2.1385369)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g2996" + style="fill-rule:evenodd;stroke:#0044aa;stroke-width:1.24747;stroke-miterlimit:4;stroke-dasharray:none"> + <polyline + fill="none" + vector-effect="" + points="0,18 135,18 " + id="polyline2994" + style="stroke:#0044aa;stroke-width:1.24747;stroke-miterlimit:4;stroke-dasharray:none" /> + </g> + </g> + <text + fill="#000000" + fill-opacity="1" + stroke="none" + xml:space="preserve" + x="236.0329" + y="14.17053" + font-family="'Noto Sans'" + font-size="12.2761px" + font-weight="400" + font-style="normal" + id="text3000" + style="fill-rule:evenodd;stroke-width:0.992009;stroke-linecap:square;stroke-linejoin:bevel;stroke-opacity:1">experiment 2</text> + <text + fill="#000000" + fill-opacity="1" + stroke="none" + xml:space="preserve" + x="218.03311" + y="32.240292" + font-family="'Noto Sans'" + font-size="12.2761px" + font-weight="400" + font-style="normal" + id="text3002" + style="fill-rule:evenodd;stroke-width:0.992009;stroke-linecap:square;stroke-linejoin:bevel;stroke-opacity:1">experimenter : Sheila</text> + <text + fill="#000000" + fill-opacity="1" + stroke="none" + xml:space="preserve" + x="218.03311" + y="50.096455" + font-family="'Noto Sans'" + font-size="12.2761px" + font-weight="400" + font-style="normal" + id="text3004" + style="fill-rule:evenodd;stroke-width:0.992009;stroke-linecap:square;stroke-linejoin:bevel;stroke-opacity:1">lab_notes : /data/dat3.</text> + <g + id="g3164" + transform="translate(-73.176703,-163.02108)"> + <rect + style="opacity:1;fill:#ffd5d5;stroke:#ff0000;stroke-width:0.839788;paint-order:markers fill stroke;stop-color:#000000" + id="rect3158" + width="15.923747" + height="13.833771" + x="289.03854" + y="167.14027" + ry="6.9168854" /> + <text + fill="#000000" + fill-opacity="1" + stroke="none" + xml:space="preserve" + x="292.86172" + y="178.57553" + font-family="'Noto Sans'" + font-size="12.2761px" + font-weight="700" + font-style="normal" + id="text3162" + style="fill:#ff0000;fill-rule:evenodd;stroke-width:0.992009"><tspan + style="fill:#ff0000" + id="tspan3160">R</tspan></text> + </g> + </g> + <g + id="g3024" + transform="translate(28.930217,27.858633)"> + <g + id="g3016"> + <g + fill="#ffffc0" + fill-opacity="1" + stroke="#ff0000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,213.32298,2.1385369)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g3010" + style="fill:#d5f6ff;fill-rule:evenodd;stroke:#0044aa;stroke-width:1.24747;stroke-miterlimit:4;stroke-dasharray:none"> + <rect + vector-effect="" + x="0" + y="0" + width="135" + height="54" + id="rect3008" + style="fill:#d5f6ff;stroke:#0044aa;stroke-width:1.24747;stroke-miterlimit:4;stroke-dasharray:none" /> + </g> + <g + fill="#ffffc0" + fill-opacity="1" + stroke="#ff0000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,213.32298,2.1385369)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g3014" + style="fill-rule:evenodd;stroke:#0044aa;stroke-width:1.24747;stroke-miterlimit:4;stroke-dasharray:none"> + <polyline + fill="none" + vector-effect="" + points="0,18 135,18 " + id="polyline3012" + style="stroke:#0044aa;stroke-width:1.24747;stroke-miterlimit:4;stroke-dasharray:none" /> + </g> + </g> + <text + fill="#000000" + fill-opacity="1" + stroke="none" + xml:space="preserve" + x="236.03297" + y="14.17053" + font-family="'Noto Sans'" + font-size="12.2761px" + font-weight="400" + font-style="normal" + id="text3018" + style="fill-rule:evenodd;stroke-width:0.992009;stroke-linecap:square;stroke-linejoin:bevel;stroke-opacity:1">experiment 3</text> + <text + fill="#000000" + fill-opacity="1" + stroke="none" + xml:space="preserve" + x="218.03311" + y="32.240292" + font-family="'Noto Sans'" + font-size="12.2761px" + font-weight="400" + font-style="normal" + id="text3020" + style="fill-rule:evenodd;stroke-width:0.992009;stroke-linecap:square;stroke-linejoin:bevel;stroke-opacity:1">experimenter : Sheila</text> + <text + fill="#000000" + fill-opacity="1" + stroke="none" + xml:space="preserve" + x="218.03311" + y="50.096455" + font-family="'Noto Sans'" + font-size="12.2761px" + font-weight="400" + font-style="normal" + id="text3022" + style="fill-rule:evenodd;stroke-width:0.992009;stroke-linecap:square;stroke-linejoin:bevel;stroke-opacity:1">lab_notes : /data/dat3.</text> + <g + id="g3164-8" + transform="translate(-72.586409,-163.21016)"> + <rect + style="opacity:1;fill:#ffd5d5;stroke:#ff0000;stroke-width:0.839788;paint-order:markers fill stroke;stop-color:#000000" + id="rect3158-3" + width="15.923747" + height="13.833771" + x="289.03854" + y="167.14027" + ry="6.9168854" /> + <text + fill="#000000" + fill-opacity="1" + stroke="none" + xml:space="preserve" + x="292.86172" + y="178.57553" + font-family="'Noto Sans'" + font-size="12.2761px" + font-weight="700" + font-style="normal" + id="text3162-4" + style="fill:#ff0000;fill-rule:evenodd;stroke-width:0.992009"><tspan + style="fill:#ff0000" + id="tspan3160-2">R</tspan></text> + </g> + </g> + <g + id="g3054" + transform="rotate(-90,117.93564,3.3018104)" + style="font-variation-settings:normal;opacity:1;vector-effect:none;fill:#d5f6ff;fill-opacity:1;stroke:#0044aa;stroke-width:1.2375;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;stop-color:#000000;stop-opacity:1"> + <g + fill="#ffffff" + fill-opacity="1" + stroke="#ff0000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="round" + stroke-linejoin="round" + transform="matrix(-0.992009,0,0,-0.992009,90.3118,64.4806)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g3048" + style="font-variation-settings:normal;vector-effect:none;fill:#d5f6ff;fill-opacity:1;fill-rule:evenodd;stroke:#0044aa;stroke-width:1.24747;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;stop-color:#000000;stop-opacity:1"> + <path + vector-effect="" + fill-rule="evenodd" + d="M -6,0 0,10 6,0 H -6" + id="path3046" + style="font-variation-settings:normal;vector-effect:none;fill:#d5f6ff;fill-opacity:1;stroke:#0044aa;stroke-width:1.24747;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;stop-color:#000000;stop-opacity:1" /> + </g> + <g + fill="none" + stroke="#ff0000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="round" + stroke-linejoin="round" + transform="matrix(0.992009,0,0,0.992009,158.491,453.661)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g3052" + style="font-variation-settings:normal;vector-effect:none;fill:#d5f6ff;fill-opacity:1;fill-rule:evenodd;stroke:#0044aa;stroke-width:1.24747;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;stop-color:#000000;stop-opacity:1"> + <path + vector-effect="" + fill-rule="evenodd" + d="m -68.7281,-363.013 v -29.302" + id="path3050" + style="font-variation-settings:normal;vector-effect:none;fill:#d5f6ff;fill-opacity:1;stroke:#0044aa;stroke-width:1.24747;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;stop-color:#000000;stop-opacity:1" /> + </g> + </g> + <g + id="g3124" + transform="translate(225.1288)"> + <g + id="g3104"> + <rect + vector-effect="" + x="0.99200898" + y="93.548401" + width="186.4977" + height="89.280807" + id="rect3100" + style="font-style:normal;font-weight:400;font-size:12.375px;font-family:'Noto Sans';font-variation-settings:normal;opacity:1;vector-effect:none;fill:#d5f6ff;fill-opacity:1;fill-rule:evenodd;stroke:#0044aa;stroke-width:1.2375;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;stop-color:#000000;stop-opacity:1" /> + <polyline + fill="none" + vector-effect="" + points="0,18 188,18 " + id="polyline3102" + style="font-style:normal;font-weight:400;font-size:12.375px;font-family:'Noto Sans';font-variation-settings:normal;opacity:1;vector-effect:none;fill:#d5f6ff;fill-opacity:1;fill-rule:evenodd;stroke:#0044aa;stroke-width:1.24747;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;stop-color:#000000;stop-opacity:1" + transform="matrix(0.992009,0,0,0.992009,0.992009,93.5484)" /> + </g> + <text + fill="#000000" + fill-opacity="1" + stroke="none" + xml:space="preserve" + x="31.640734" + y="106.94052" + font-family="'Noto Sans'" + font-size="12.2761px" + font-weight="700" + font-style="normal" + id="text3106" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'Noto Sans';-inkscape-font-specification:'Noto Sans';fill-rule:evenodd;stroke-width:0.992009;stroke-linecap:square;stroke-linejoin:bevel;stroke-opacity:1">my_exp 1</text> + <text + fill="#000000" + fill-opacity="1" + stroke="none" + xml:space="preserve" + x="5.952054" + y="124.79668" + font-family="'Noto Sans'" + font-size="12.2761px" + font-weight="400" + font-style="normal" + id="text3108" + style="fill-rule:evenodd;stroke-width:0.992009;stroke-linecap:square;stroke-linejoin:bevel;stroke-opacity:1">experimenter : Paul</text> + <text + fill="#000000" + fill-opacity="1" + stroke="none" + xml:space="preserve" + x="5.952054" + y="142.65285" + font-family="'Noto Sans'" + font-size="12.2761px" + font-weight="400" + font-style="normal" + id="text3110" + style="fill-rule:evenodd;stroke-width:0.992009;stroke-linecap:square;stroke-linejoin:bevel;stroke-opacity:1">lab_notes : /data/cc/dat3.dat</text> + <text + fill="#000000" + fill-opacity="1" + stroke="none" + xml:space="preserve" + x="5.952054" + y="160.509" + font-family="'Noto Sans'" + font-size="12.2761px" + font-weight="400" + font-style="normal" + id="text3112" + style="fill-rule:evenodd;stroke-width:0.992009;stroke-linecap:square;stroke-linejoin:bevel;stroke-opacity:1">number_of_dishes : 5</text> + <text + fill="#000000" + fill-opacity="1" + stroke="none" + xml:space="preserve" + x="5.952054" + y="178.36517" + font-family="'Noto Sans'" + font-size="12.2761px" + font-weight="400" + font-style="normal" + id="text3114" + style="fill-rule:evenodd;stroke-width:0.992009;stroke-linecap:square;stroke-linejoin:bevel;stroke-opacity:1">cell_line : #123</text> + <g + id="g3122" + transform="translate(-285.2144,-71.568939)"> + <rect + style="opacity:1;fill:#ffd5d5;stroke:#ff0000;stroke-width:0.839788;paint-order:markers fill stroke;stop-color:#000000" + id="rect3116" + width="15.923747" + height="13.833771" + x="289.03854" + y="167.14027" + ry="6.9168854" /> + <text + fill="#000000" + fill-opacity="1" + stroke="none" + xml:space="preserve" + x="292.86172" + y="178.57553" + font-family="'Noto Sans'" + font-size="12.2761px" + font-weight="700" + font-style="normal" + id="text3120" + style="fill:#ff0000;fill-rule:evenodd;stroke-width:0.992009"><tspan + style="fill:#ff0000" + id="tspan3118">R</tspan></text> + </g> + </g> + <g + id="g3259" + transform="translate(243.88467,15.856705)"> + <g + id="g3239"> + <rect + vector-effect="" + x="0.99200898" + y="93.548401" + width="186.4977" + height="89.280807" + id="rect3235" + style="font-style:normal;font-weight:400;font-size:12.375px;font-family:'Noto Sans';font-variation-settings:normal;opacity:1;vector-effect:none;fill:#d5f6ff;fill-opacity:1;fill-rule:evenodd;stroke:#0044aa;stroke-width:1.2375;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;stop-color:#000000;stop-opacity:1" /> + <polyline + fill="none" + vector-effect="" + points="0,18 188,18 " + id="polyline3237" + style="font-style:normal;font-weight:400;font-size:12.375px;font-family:'Noto Sans';font-variation-settings:normal;opacity:1;vector-effect:none;fill:#d5f6ff;fill-opacity:1;fill-rule:evenodd;stroke:#0044aa;stroke-width:1.24747;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;stop-color:#000000;stop-opacity:1" + transform="matrix(0.992009,0,0,0.992009,0.992009,93.5484)" /> + </g> + <text + fill="#000000" + fill-opacity="1" + stroke="none" + xml:space="preserve" + x="31.640734" + y="106.94052" + font-family="'Noto Sans'" + font-size="12.2761px" + font-weight="700" + font-style="normal" + id="text3241" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'Noto Sans';-inkscape-font-specification:'Noto Sans';fill-rule:evenodd;stroke-width:0.992009;stroke-linecap:square;stroke-linejoin:bevel;stroke-opacity:1">my_exp 2</text> + <text + fill="#000000" + fill-opacity="1" + stroke="none" + xml:space="preserve" + x="5.952054" + y="124.79668" + font-family="'Noto Sans'" + font-size="12.2761px" + font-weight="400" + font-style="normal" + id="text3243" + style="fill-rule:evenodd;stroke-width:0.992009;stroke-linecap:square;stroke-linejoin:bevel;stroke-opacity:1">experimenter : Paul</text> + <text + fill="#000000" + fill-opacity="1" + stroke="none" + xml:space="preserve" + x="5.952054" + y="142.65285" + font-family="'Noto Sans'" + font-size="12.2761px" + font-weight="400" + font-style="normal" + id="text3245" + style="fill-rule:evenodd;stroke-width:0.992009;stroke-linecap:square;stroke-linejoin:bevel;stroke-opacity:1">lab_notes : /data/cc/dat3.dat</text> + <text + fill="#000000" + fill-opacity="1" + stroke="none" + xml:space="preserve" + x="5.952054" + y="160.509" + font-family="'Noto Sans'" + font-size="12.2761px" + font-weight="400" + font-style="normal" + id="text3247" + style="fill-rule:evenodd;stroke-width:0.992009;stroke-linecap:square;stroke-linejoin:bevel;stroke-opacity:1">number_of_dishes : 5</text> + <text + fill="#000000" + fill-opacity="1" + stroke="none" + xml:space="preserve" + x="5.952054" + y="178.36517" + font-family="'Noto Sans'" + font-size="12.2761px" + font-weight="400" + font-style="normal" + id="text3249" + style="fill-rule:evenodd;stroke-width:0.992009;stroke-linecap:square;stroke-linejoin:bevel;stroke-opacity:1">cell_line : #123</text> + <g + id="g3257" + transform="translate(-285.2144,-71.568939)"> + <rect + style="opacity:1;fill:#ffd5d5;stroke:#ff0000;stroke-width:0.839788;paint-order:markers fill stroke;stop-color:#000000" + id="rect3251" + width="15.923747" + height="13.833771" + x="289.03854" + y="167.14027" + ry="6.9168854" /> + <text + fill="#000000" + fill-opacity="1" + stroke="none" + xml:space="preserve" + x="292.86172" + y="178.57553" + font-family="'Noto Sans'" + font-size="12.2761px" + font-weight="700" + font-style="normal" + id="text3255" + style="fill:#ff0000;fill-rule:evenodd;stroke-width:0.992009"><tspan + style="fill:#ff0000" + id="tspan3253">R</tspan></text> + </g> + </g> + <g + id="g3285" + transform="translate(259.80841,33.815492)"> + <g + id="g3265"> + <rect + vector-effect="" + x="0.99200898" + y="93.548401" + width="186.4977" + height="89.280807" + id="rect3261" + style="font-style:normal;font-weight:400;font-size:12.375px;font-family:'Noto Sans';font-variation-settings:normal;opacity:1;vector-effect:none;fill:#d5f6ff;fill-opacity:1;fill-rule:evenodd;stroke:#0044aa;stroke-width:1.2375;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;stop-color:#000000;stop-opacity:1" /> + <polyline + fill="none" + vector-effect="" + points="0,18 188,18 " + id="polyline3263" + style="font-style:normal;font-weight:400;font-size:12.375px;font-family:'Noto Sans';font-variation-settings:normal;opacity:1;vector-effect:none;fill:#d5f6ff;fill-opacity:1;fill-rule:evenodd;stroke:#0044aa;stroke-width:1.24747;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;stop-color:#000000;stop-opacity:1" + transform="matrix(0.992009,0,0,0.992009,0.992009,93.5484)" /> + </g> + <text + fill="#000000" + fill-opacity="1" + stroke="none" + xml:space="preserve" + x="31.640734" + y="106.94052" + font-family="'Noto Sans'" + font-size="12.2761px" + font-weight="700" + font-style="normal" + id="text3267" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'Noto Sans';-inkscape-font-specification:'Noto Sans';fill-rule:evenodd;stroke-width:0.992009;stroke-linecap:square;stroke-linejoin:bevel;stroke-opacity:1">my_exp 3</text> + <text + fill="#000000" + fill-opacity="1" + stroke="none" + xml:space="preserve" + x="5.952054" + y="124.79668" + font-family="'Noto Sans'" + font-size="12.2761px" + font-weight="400" + font-style="normal" + id="text3269" + style="fill-rule:evenodd;stroke-width:0.992009;stroke-linecap:square;stroke-linejoin:bevel;stroke-opacity:1">experimenter : Paul</text> + <text + fill="#000000" + fill-opacity="1" + stroke="none" + xml:space="preserve" + x="5.952054" + y="142.65285" + font-family="'Noto Sans'" + font-size="12.2761px" + font-weight="400" + font-style="normal" + id="text3271" + style="fill-rule:evenodd;stroke-width:0.992009;stroke-linecap:square;stroke-linejoin:bevel;stroke-opacity:1">lab_notes : /data/cc/dat3.dat</text> + <text + fill="#000000" + fill-opacity="1" + stroke="none" + xml:space="preserve" + x="5.952054" + y="160.509" + font-family="'Noto Sans'" + font-size="12.2761px" + font-weight="400" + font-style="normal" + id="text3273" + style="fill-rule:evenodd;stroke-width:0.992009;stroke-linecap:square;stroke-linejoin:bevel;stroke-opacity:1">number_of_dishes : 5</text> + <text + fill="#000000" + fill-opacity="1" + stroke="none" + xml:space="preserve" + x="5.952054" + y="178.36517" + font-family="'Noto Sans'" + font-size="12.2761px" + font-weight="400" + font-style="normal" + id="text3275" + style="fill-rule:evenodd;stroke-width:0.992009;stroke-linecap:square;stroke-linejoin:bevel;stroke-opacity:1">cell_line : #123</text> + <g + id="g3283" + transform="translate(-285.2144,-71.568939)"> + <rect + style="opacity:1;fill:#ffd5d5;stroke:#ff0000;stroke-width:0.839788;paint-order:markers fill stroke;stop-color:#000000" + id="rect3277" + width="15.923747" + height="13.833771" + x="289.03854" + y="167.14027" + ry="6.9168854" /> + <text + fill="#000000" + fill-opacity="1" + stroke="none" + xml:space="preserve" + x="292.86172" + y="178.57553" + font-family="'Noto Sans'" + font-size="12.2761px" + font-weight="700" + font-style="normal" + id="text3281" + style="fill:#ff0000;fill-rule:evenodd;stroke-width:0.992009"><tspan + style="fill:#ff0000" + id="tspan3279">R</tspan></text> + </g> + </g> + <g + id="g3295" + transform="rotate(-90,181.47795,44.53676)" + style="font-variation-settings:normal;opacity:1;vector-effect:none;fill:#d5f6ff;fill-opacity:1;stroke:#0044aa;stroke-width:1.2375;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;stop-color:#000000;stop-opacity:1"> + <g + fill="#ffffff" + fill-opacity="1" + stroke="#ff0000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="round" + stroke-linejoin="round" + transform="matrix(-0.992009,0,0,-0.992009,90.3118,64.4806)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g3289" + style="font-variation-settings:normal;vector-effect:none;fill:#d5f6ff;fill-opacity:1;fill-rule:evenodd;stroke:#0044aa;stroke-width:1.24747;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;stop-color:#000000;stop-opacity:1"> + <path + vector-effect="" + fill-rule="evenodd" + d="M -6,0 0,10 6,0 H -6" + id="path3287" + style="font-variation-settings:normal;vector-effect:none;fill:#d5f6ff;fill-opacity:1;stroke:#0044aa;stroke-width:1.24747;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;stop-color:#000000;stop-opacity:1" /> + </g> + <g + fill="none" + stroke="#ff0000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="round" + stroke-linejoin="round" + transform="matrix(0.992009,0,0,0.992009,158.491,453.661)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g3293" + style="font-variation-settings:normal;vector-effect:none;fill:#d5f6ff;fill-opacity:1;fill-rule:evenodd;stroke:#0044aa;stroke-width:1.24747;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;stop-color:#000000;stop-opacity:1"> + <path + vector-effect="" + fill-rule="evenodd" + d="M -68.7281,-375.23969 V -392.315" + id="path3291" + style="font-variation-settings:normal;vector-effect:none;fill:#d5f6ff;fill-opacity:1;stroke:#0044aa;stroke-width:1.24747;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;stop-color:#000000;stop-opacity:1" + sodipodi:nodetypes="cc" /> + </g> + </g> + <text + xml:space="preserve" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:16.5001px;line-height:1.25;font-family:Stanberry;-inkscape-font-specification:Stanberry;letter-spacing:0px;word-spacing:0px;fill:#0000ff;fill-opacity:1;stroke:none;stroke-width:0.332398" + x="421.51617" + y="20.97814" + id="text1517"><tspan + sodipodi:role="line" + id="tspan1515" + x="421.51617" + y="20.97814" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:16.5001px;font-family:Stanberry;-inkscape-font-specification:Stanberry;fill:#0000ff;stroke-width:0.332398">Records</tspan></text> + <path + style="fill:none;stroke:#0000ff;stroke-width:0.839441;stroke-miterlimit:4;stroke-dasharray:none;marker-end:url(#marker1523-5);paint-order:markers fill stroke;stop-color:#000000" + d="m 441.22143,27.507131 c -1.42777,5.850954 -9.22389,11.574261 -17.31854,13.818563 -12.822,3.554995 -11.11495,0.07953 -31.34267,1.84685" + id="path1519" + sodipodi:nodetypes="csc" /> + <path + style="fill:none;stroke:#0000ff;stroke-width:0.839441;stroke-miterlimit:4;stroke-dasharray:none;marker-end:url(#marker3403);paint-order:markers fill stroke;stop-color:#000000" + d="m 454.89862,27.912518 c 1.72938,21.926674 0.42202,29.71883 -3.7148,37.029573 -6.55276,11.580287 -13.10909,16.963606 -26.16255,32.516469" + id="path3399" + sodipodi:nodetypes="csc" /> + </g> +</svg> diff --git a/src/doc/tutorials/img/edit_entity_button.png b/src/doc/tutorials/img/edit_entity_button.png new file mode 100644 index 0000000000000000000000000000000000000000..e7b1421035340c3351ea4bfdc25ed7cda9579ff1 Binary files /dev/null and b/src/doc/tutorials/img/edit_entity_button.png differ diff --git a/src/doc/tutorials/img/edit_mode_button.png b/src/doc/tutorials/img/edit_mode_button.png new file mode 100644 index 0000000000000000000000000000000000000000..f3764ba3af3caed2d4ae2ecf430f0c8275cd068b Binary files /dev/null and b/src/doc/tutorials/img/edit_mode_button.png differ diff --git a/src/doc/tutorials/img/screenshot_dropdown.png b/src/doc/tutorials/img/screenshot_dropdown.png new file mode 100644 index 0000000000000000000000000000000000000000..3b4e65c6ce2a70ba8a5cede12ab11bc7ef0b68d5 Binary files /dev/null and b/src/doc/tutorials/img/screenshot_dropdown.png differ diff --git a/src/doc/tutorials/img/screenshot_edit_create_delete.png b/src/doc/tutorials/img/screenshot_edit_create_delete.png new file mode 100644 index 0000000000000000000000000000000000000000..099efa9ff28569f494e9ef543e6396e6af10e1b1 Binary files /dev/null and b/src/doc/tutorials/img/screenshot_edit_create_delete.png differ diff --git a/src/doc/tutorials/img/screenshot_menu.png b/src/doc/tutorials/img/screenshot_menu.png new file mode 100644 index 0000000000000000000000000000000000000000..10288582cdddec16f59deaa69868538f31729ab4 Binary files /dev/null and b/src/doc/tutorials/img/screenshot_menu.png differ diff --git a/src/doc/tutorials/img/screenshot_new_recordtype.png b/src/doc/tutorials/img/screenshot_new_recordtype.png new file mode 100644 index 0000000000000000000000000000000000000000..ddb105d6d689e3b6495bdb3ec60673cb4140ca8e Binary files /dev/null and b/src/doc/tutorials/img/screenshot_new_recordtype.png differ diff --git a/src/doc/tutorials/img/simple_data_model.png b/src/doc/tutorials/img/simple_data_model.png new file mode 100644 index 0000000000000000000000000000000000000000..caa783aca63aac65bfea62e3f92d830bcb1b17f5 Binary files /dev/null and b/src/doc/tutorials/img/simple_data_model.png differ diff --git a/src/doc/tutorials/img/simple_data_model.svg b/src/doc/tutorials/img/simple_data_model.svg new file mode 100644 index 0000000000000000000000000000000000000000..80f7ddce12d7a85ceac8952b861fb7433676b486 --- /dev/null +++ b/src/doc/tutorials/img/simple_data_model.svg @@ -0,0 +1,1826 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="113.145mm" + height="72.8646mm" + viewBox="0 0 441 284" + version="1.2" + id="svg236" + sodipodi:docname="simple_data_model.svg" + inkscape:version="1.0.2 (e86c870879, 2021-01-15)" + inkscape:export-filename="/home/daniel/indiscale/management/external/bmpg/management/documentation/data_model/how_to_datamodel/simple_data_model.png" + inkscape:export-xdpi="448.98001" + inkscape:export-ydpi="448.98001"> + <metadata + id="metadata240"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title>Qt SVG Document</dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1920" + inkscape:window-height="1135" + id="namedview238" + showgrid="false" + inkscape:zoom="1.8023479" + inkscape:cx="156.06897" + inkscape:cy="185.47266" + inkscape:window-x="0" + inkscape:window-y="0" + inkscape:window-maximized="1" + inkscape:current-layer="layer1" + inkscape:document-rotation="0" /> + <title + id="title2">Qt SVG Document</title> + <desc + id="desc4">Generated with Qt</desc> + <defs + id="defs6"> + <marker + style="overflow:visible;" + id="marker1523" + refX="0.0" + refY="0.0" + orient="auto" + inkscape:stockid="Arrow2Lend" + inkscape:isstock="true"> + <path + transform="scale(1.1) rotate(180) translate(1,0)" + d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z " + style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#0000ff;stroke-opacity:1;fill:#0000ff;fill-opacity:1" + id="path1521" /> + </marker> + <marker + style="overflow:visible;" + id="marker1423" + refX="0.0" + refY="0.0" + orient="auto" + inkscape:stockid="Arrow2Lend" + inkscape:isstock="true" + inkscape:collect="always"> + <path + transform="scale(1.1) rotate(180) translate(1,0)" + d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z " + style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#0000ff;stroke-opacity:1;fill:#0000ff;fill-opacity:1" + id="path1421" /> + </marker> + <marker + style="overflow:visible;" + id="marker1333" + refX="0.0" + refY="0.0" + orient="auto" + inkscape:stockid="Arrow2Mend" + inkscape:isstock="true"> + <path + transform="scale(0.6) rotate(180) translate(0,0)" + d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z " + style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#0000ff;stroke-opacity:1;fill:#0000ff;fill-opacity:1" + id="path1331" /> + </marker> + <marker + style="overflow:visible;" + id="Arrow2Lend" + refX="0.0" + refY="0.0" + orient="auto" + inkscape:stockid="Arrow2Lend" + inkscape:isstock="true" + inkscape:collect="always"> + <path + transform="scale(1.1) rotate(180) translate(1,0)" + d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z " + style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#0000ff;stroke-opacity:1;fill:#0000ff;fill-opacity:1" + id="path988" /> + </marker> + <marker + style="overflow:visible;" + id="Arrow1Mend" + refX="0.0" + refY="0.0" + orient="auto" + inkscape:stockid="Arrow1Mend" + inkscape:isstock="true"> + <path + transform="scale(0.4) rotate(180) translate(10,0)" + style="fill-rule:evenodd;stroke:#0000ff;stroke-width:1pt;stroke-opacity:1;fill:#0000ff;fill-opacity:1" + d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " + id="path976" /> + </marker> + </defs> + <g + inkscape:groupmode="layer" + id="layer1" + inkscape:label="Layer 1"> + <g + fill="none" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g8" + style="fill-rule:evenodd;" /> + <g + fill="none" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g10" + style="fill-rule:evenodd;" /> + <g + fill="none" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,158.491,453.661)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g12" + style="fill-rule:evenodd;" /> + <g + fill="none" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,158.491,453.661)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g18" + style="fill-rule:evenodd;" /> + <g + fill="none" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,0.992009,93.5484)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g20" + style="fill-rule:evenodd;" /> + <g + fill="none" + stroke="#ff0000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="round" + stroke-linejoin="round" + transform="matrix(0.992009,0,0,0.992009,158.491,454.51927)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g86" + style="fill-rule:evenodd;stroke-width:1.24747;stroke-miterlimit:4;stroke-dasharray:none"> + <path + vector-effect="" + fill-rule="evenodd" + d="m -68.7281,-363.013 v -29.302" + id="path84" + style="stroke:#ff0000;stroke-width:1.24747;stroke-miterlimit:4;stroke-dasharray:none" /> + </g> + <g + fill="#ffffc0" + fill-opacity="1" + stroke="#ff0000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,0.992009,93.5484)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g24" + style="fill-rule:evenodd;stroke-width:1.24747354;stroke-miterlimit:4;stroke-dasharray:none"> + <rect + vector-effect="" + x="0" + y="0" + width="188" + height="90" + id="rect22" + style="stroke-width:1.24747354;stroke:#ff0000;stroke-miterlimit:4;stroke-dasharray:none" /> + </g> + <g + fill="#ffffc0" + fill-opacity="1" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,0.992009,93.5484)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="700" + font-style="normal" + id="g26" + style="fill-rule:evenodd;" /> + <g + fill="#ffffc0" + fill-opacity="1" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,11.30455,93.5484)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="700" + font-style="normal" + id="g30" + style="fill-rule:evenodd;"> + <text + fill="#000000" + fill-opacity="1" + stroke="none" + xml:space="preserve" + x="20.5" + y="13.5" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="700" + font-style="normal" + id="text28">CellCultureExperiment</text> + </g> + <g + fill="#ffffc0" + fill-opacity="1" + stroke="#ff0000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,0.992009,93.5484)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g34" + style="fill-rule:evenodd;stroke-width:1.24747354;stroke-miterlimit:4;stroke-dasharray:none"> + <polyline + fill="none" + vector-effect="" + points="0,18 188,18 " + id="polyline32" + style="stroke-width:1.24747354;stroke:#ff0000;stroke-miterlimit:4;stroke-dasharray:none" /> + </g> + <g + fill="#ffffc0" + fill-opacity="1" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,0.992009,93.5484)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g36" + style="fill-rule:evenodd;" /> + <g + fill="#ffffc0" + fill-opacity="1" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,0.992009,93.5484)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g38" + style="fill-rule:evenodd;" /> + <g + fill="#ffffc0" + fill-opacity="1" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,0.992009,93.5484)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g42" + style="fill-rule:evenodd;"> + <text + fill="#000000" + fill-opacity="1" + stroke="none" + xml:space="preserve" + x="5" + y="31.5" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="text40">experimenter : TEXT</text> + </g> + <g + fill="#ffffc0" + fill-opacity="1" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,0.992009,93.5484)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g44" + style="fill-rule:evenodd;" /> + <g + fill="#ffffc0" + fill-opacity="1" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,0.992009,93.5484)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g48" + style="fill-rule:evenodd;"> + <text + fill="#000000" + fill-opacity="1" + stroke="none" + xml:space="preserve" + x="5" + y="49.5" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="text46">lab_notes : FILE</text> + </g> + <g + fill="#ffffc0" + fill-opacity="1" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,0.992009,93.5484)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g50" + style="fill-rule:evenodd;" /> + <g + fill="#ffffc0" + fill-opacity="1" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,0.992009,93.5484)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g54" + style="fill-rule:evenodd;"> + <text + fill="#000000" + fill-opacity="1" + stroke="none" + xml:space="preserve" + x="5" + y="67.5" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="text52">number_of_dishes : INTEGER</text> + </g> + <g + fill="#ffffc0" + fill-opacity="1" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,0.992009,93.5484)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g56" + style="fill-rule:evenodd;" /> + <g + fill="#ffffc0" + fill-opacity="1" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,0.992009,93.5484)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g60" + style="fill-rule:evenodd;"> + <text + fill="#000000" + fill-opacity="1" + stroke="none" + xml:space="preserve" + x="5" + y="85.5" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="text58">cell_line : CellLine</text> + </g> + <g + fill="#ffffc0" + fill-opacity="1" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,0.992009,93.5484)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g62" + style="fill-rule:evenodd;" /> + <g + fill="#ffffc0" + fill-opacity="1" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,0.992009,93.5484)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g64" + style="fill-rule:evenodd;" /> + <g + fill="#ffffc0" + fill-opacity="1" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g66" + style="fill-rule:evenodd;" /> + <g + fill="none" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,0.992009,93.5484)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g68" + style="fill-rule:evenodd;" /> + <g + fill="none" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,158.491,453.661)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g70" + style="fill-rule:evenodd;" /> + <g + fill="none" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,158.491,453.661)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g72" + style="fill-rule:evenodd;" /> + <g + fill="none" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(-0.992009,0,0,-0.992009,90.3118,64.4806)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g74" + style="fill-rule:evenodd;" /> + <g + fill="#ffffff" + fill-opacity="1" + stroke="#ff0000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="round" + stroke-linejoin="round" + transform="matrix(-0.992009,0,0,-0.992009,90.3118,65.338858)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g78" + style="fill-rule:evenodd;stroke-width:1.24747;stroke-miterlimit:4;stroke-dasharray:none"> + <path + vector-effect="" + fill-rule="evenodd" + d="M -6,0 0,10 6,0 H -6" + id="path76" + style="stroke:#ff0000;stroke-width:1.24747;stroke-miterlimit:4;stroke-dasharray:none" /> + </g> + <g + fill="none" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(-0.992009,0,0,-0.992009,90.3118,64.4806)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g80" + style="fill-rule:evenodd;" /> + <g + fill="none" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,158.491,453.661)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g82" + style="fill-rule:evenodd;" /> + <g + fill="none" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,158.491,453.661)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g88" + style="fill-rule:evenodd;" /> + <g + fill="none" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,158.491,453.661)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g90" + style="fill-rule:evenodd;" /> + <g + fill="none" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,158.491,453.661)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g92" + style="fill-rule:evenodd;" /> + <g + fill="none" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0,0.992009,-0.992009,0,197.41,178.126)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g94" + style="fill-rule:evenodd;" /> + <g + fill="#ff0000" + fill-opacity="1" + stroke="#ff0000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="round" + stroke-linejoin="round" + transform="matrix(0,0.992009,-0.992009,0,197.41,178.126)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g98" + style="fill-rule:evenodd;stroke-width:1.24747354;stroke-miterlimit:4;stroke-dasharray:none"> + <path + vector-effect="" + fill-rule="evenodd" + d="M 0,-12.307128 -5,-2.3071266 0,7.6928736 5,-2.3071266 0,-12.307128" + id="path96" + style="stroke:#ff0000;stroke-width:1.24747;stroke-miterlimit:4;stroke-dasharray:none" /> + </g> + <g + fill="none" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0,0.992009,-0.992009,0,197.41,178.126)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g100" + style="fill-rule:evenodd;" /> + <g + fill="none" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,158.491,453.661)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g102" + style="fill-rule:evenodd;" /> + <g + fill="none" + stroke="#ff0000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="round" + stroke-linejoin="round" + transform="matrix(0.992009,0,0,0.992009,158.491,453.661)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g106" + style="fill-rule:evenodd;stroke-width:1.24747354;stroke-miterlimit:4;stroke-dasharray:none"> + <path + vector-effect="" + fill-rule="evenodd" + d="m 49.2327,-277.754 80.94846,-16.18789" + id="path104" + style="stroke:#ff0000;stroke-width:1.24747;stroke-miterlimit:4;stroke-dasharray:none" + sodipodi:nodetypes="cc" /> + </g> + <g + fill="none" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,158.491,453.661)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g108" + style="fill-rule:evenodd;" /> + <g + fill="none" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,158.491,453.661)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g110" + style="fill-rule:evenodd;" /> + <g + fill="none" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,158.491,453.661)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g112" + style="fill-rule:evenodd;" /> + <g + fill="none" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(-0.992009,0,0,-0.992009,319.539,211.365)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g114" + style="fill-rule:evenodd;" /> + <g + fill="#ff0000" + fill-opacity="1" + stroke="#ff0000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="round" + stroke-linejoin="round" + transform="matrix(-0.992009,0,0,-0.992009,319.539,212.22327)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g118" + style="fill-rule:evenodd;stroke-width:1.24747;stroke-miterlimit:4;stroke-dasharray:none"> + <path + vector-effect="" + fill-rule="evenodd" + d="M 0,-10 -5,0 0,10 5,0 0,-10" + id="path116" + style="stroke:#ff0000;stroke-width:1.24747;stroke-miterlimit:4;stroke-dasharray:none" /> + </g> + <g + fill="none" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(-0.992009,0,0,-0.992009,319.539,211.365)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g120" + style="fill-rule:evenodd;" /> + <g + fill="none" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,158.491,453.661)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g122" + style="fill-rule:evenodd;" /> + <g + fill="none" + stroke="#ff0000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="round" + stroke-linejoin="round" + transform="matrix(0.992009,0,0,0.992009,158.491,453.661)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g126" + style="fill-rule:evenodd;stroke-width:1.24747354;stroke-miterlimit:4;stroke-dasharray:none"> + <path + vector-effect="" + fill-rule="evenodd" + d="m 162.345,-234.247 v 26.22" + id="path124" + style="stroke-width:1.24747354;stroke:#ff0000;stroke-miterlimit:4;stroke-dasharray:none" /> + </g> + <g + fill="none" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,158.491,453.661)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g128" + style="fill-rule:evenodd;" /> + <g + fill="none" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,24.4333,0.992009)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g130" + style="fill-rule:evenodd;" /> + <g + fill="#ffffc0" + fill-opacity="1" + stroke="#ff0000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,24.4333,0.992009)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g134" + style="fill-rule:evenodd;stroke-width:1.24747354;stroke-miterlimit:4;stroke-dasharray:none"> + <rect + vector-effect="" + x="0" + y="0" + width="135" + height="54" + id="rect132" + style="stroke-width:1.24747354;stroke:#ff0000;stroke-miterlimit:4;stroke-dasharray:none" /> + </g> + <g + fill="#ffffc0" + fill-opacity="1" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,24.4333,0.992009)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="700" + font-style="normal" + id="g136" + style="fill-rule:evenodd;" /> + <g + fill="#ffffc0" + fill-opacity="1" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,24.4333,0.992009)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="700" + font-style="normal" + id="g140" + style="fill-rule:evenodd;"> + <text + fill="#000000" + fill-opacity="1" + stroke="none" + xml:space="preserve" + x="30.5" + y="13.5" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="700" + font-style="normal" + id="text138">Experiment</text> + </g> + <g + fill="#ffffc0" + fill-opacity="1" + stroke="#ff0000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,24.4333,0.992009)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g144" + style="fill-rule:evenodd;stroke-width:1.24747354;stroke-miterlimit:4;stroke-dasharray:none"> + <polyline + fill="none" + vector-effect="" + points="0,18 135,18 " + id="polyline142" + style="stroke-width:1.24747354;stroke:#ff0000;stroke-miterlimit:4;stroke-dasharray:none" /> + </g> + <g + fill="#ffffc0" + fill-opacity="1" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,24.4333,0.992009)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g146" + style="fill-rule:evenodd;" /> + <g + fill="#ffffc0" + fill-opacity="1" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,24.4333,0.992009)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g148" + style="fill-rule:evenodd;" /> + <g + fill="#ffffc0" + fill-opacity="1" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,24.4333,0.992009)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g152" + style="fill-rule:evenodd;"> + <text + fill="#000000" + fill-opacity="1" + stroke="none" + xml:space="preserve" + x="5" + y="31.5" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="text150">experimenter : TEXT</text> + </g> + <g + fill="#ffffc0" + fill-opacity="1" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,24.4333,0.992009)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g154" + style="fill-rule:evenodd;" /> + <g + fill="#ffffc0" + fill-opacity="1" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,24.4333,0.992009)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g158" + style="fill-rule:evenodd;"> + <text + fill="#000000" + fill-opacity="1" + stroke="none" + xml:space="preserve" + x="5" + y="49.5" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="text156">lab_notes : FILE</text> + </g> + <g + fill="#ffffc0" + fill-opacity="1" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,24.4333,0.992009)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g160" + style="fill-rule:evenodd;" /> + <g + fill="#ffffc0" + fill-opacity="1" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,24.4333,0.992009)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g162" + style="fill-rule:evenodd;" /> + <g + fill="#ffffc0" + fill-opacity="1" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g164" + style="fill-rule:evenodd;" /> + <g + fill="none" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,24.4333,0.992009)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g166" + style="fill-rule:evenodd;" /> + <g + fill="none" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,243.928,247.296)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g168" + style="fill-rule:evenodd;" /> + <g + fill="#ffffc0" + fill-opacity="1" + stroke="#ff0000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,243.928,247.296)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g172" + style="fill-rule:evenodd;stroke-width:1.24747354;stroke-miterlimit:4;stroke-dasharray:none"> + <rect + vector-effect="" + x="0" + y="0" + width="100.93769" + height="36" + id="rect170" + style="stroke-width:1.24747354;stroke:#ff0000;stroke-miterlimit:4;stroke-dasharray:none" /> + </g> + <g + fill="#ffffc0" + fill-opacity="1" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,243.928,247.296)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="700" + font-style="normal" + id="g174" + style="fill-rule:evenodd;" /> + <g + fill="#ffffc0" + fill-opacity="1" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,254.24055,247.296)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="700" + font-style="normal" + id="g178" + style="fill-rule:evenodd;"> + <text + fill="#000000" + fill-opacity="1" + stroke="none" + xml:space="preserve" + x="15.237368" + y="13.5" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="700" + font-style="normal" + id="text176">Publication</text> + </g> + <g + fill="#ffffc0" + fill-opacity="1" + stroke="#ff0000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,243.928,247.296)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g182" + style="fill-rule:evenodd;stroke-width:1.24747354;stroke-miterlimit:4;stroke-dasharray:none"> + <path + id="polyline180" + style="stroke-width:1.24747354;stroke-miterlimit:4;stroke-dasharray:none" + d="m 0,18 100.87997,0" + sodipodi:nodetypes="cc" /> + </g> + <g + fill="#ffffc0" + fill-opacity="1" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,243.928,247.296)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g184" + style="fill-rule:evenodd;" /> + <g + fill="#ffffc0" + fill-opacity="1" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,243.928,247.296)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g186" + style="fill-rule:evenodd;" /> + <g + fill="#ffffc0" + fill-opacity="1" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,243.928,247.296)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g190" + style="fill-rule:evenodd;"> + <text + fill="#000000" + fill-opacity="1" + stroke="none" + xml:space="preserve" + x="5" + y="31.5" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="text188">author : TEXT</text> + </g> + <g + fill="#ffffc0" + fill-opacity="1" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,243.928,247.296)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g192" + style="fill-rule:evenodd;" /> + <g + fill="#ffffc0" + fill-opacity="1" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,243.928,247.296)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g194" + style="fill-rule:evenodd;" /> + <g + fill="#ffffc0" + fill-opacity="1" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g196" + style="fill-rule:evenodd;" /> + <g + fill="none" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,243.928,247.296)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g198" + style="fill-rule:evenodd;" /> + <g + fill="none" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,288.811,165.733)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g200" + style="fill-rule:evenodd;" /> + <g + fill="#ffffc0" + fill-opacity="1" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,288.811,165.733)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="700" + font-style="normal" + id="g206" + style="fill-rule:evenodd;" /> + <g + fill="#ffffc0" + fill-opacity="1" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,288.811,165.733)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g216" + style="fill-rule:evenodd;" /> + <g + fill="#ffffc0" + fill-opacity="1" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,288.811,165.733)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g218" + style="fill-rule:evenodd;" /> + <g + fill="#ffffc0" + fill-opacity="1" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,288.811,165.733)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g224" + style="fill-rule:evenodd;" /> + <g + fill="#ffffc0" + fill-opacity="1" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,288.811,165.733)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g226" + style="fill-rule:evenodd;" /> + <g + fill="#ffffc0" + fill-opacity="1" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g228" + style="fill-rule:evenodd;" /> + <g + fill="none" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,288.811,165.733)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g230" + style="fill-rule:evenodd;" /> + <g + fill="none" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g232" + style="fill-rule:evenodd;" /> + <g + id="g1454" + transform="translate(-42.278435,82.178661)"> + <rect + style="opacity:1;fill:#d7f4d7;stroke:#008000;stroke-width:0.839788;paint-order:markers fill stroke;stop-color:#000000;" + id="rect1448" + width="18.926079" + height="13.833775" + x="289.03854" + y="167.14027" + ry="4.099587" /> + <text + fill="#000000" + fill-opacity="1" + stroke="none" + xml:space="preserve" + x="290.79922" + y="178.57553" + font-family="'Noto Sans'" + font-size="12.2761px" + font-weight="700" + font-style="normal" + id="text1452" + style="fill-rule:evenodd;stroke-width:0.992009;"><tspan + style="fill:#008000;" + id="tspan1450">RT</tspan></text> + </g> + <g + id="g1462" + transform="translate(-285.2144,-71.568939)"> + <rect + style="opacity:1;fill:#d7f4d7;stroke:#008000;stroke-width:0.839788;paint-order:markers fill stroke;stop-color:#000000;" + id="rect1456" + width="18.926079" + height="13.833775" + x="289.03854" + y="167.14027" + ry="4.099587" /> + <text + fill="#000000" + fill-opacity="1" + stroke="none" + xml:space="preserve" + x="290.79922" + y="178.57553" + font-family="'Noto Sans'" + font-size="12.2761px" + font-weight="700" + font-style="normal" + id="text1460" + style="fill-rule:evenodd;stroke-width:0.992009;"><tspan + style="fill:#008000;" + id="tspan1458">RT</tspan></text> + </g> + <g + id="g1470" + transform="translate(-261.77311,-164.12532)"> + <rect + style="opacity:1;fill:#d7f4d7;stroke:#008000;stroke-width:0.839788;paint-order:markers fill stroke;stop-color:#000000;" + id="rect1464" + width="18.926079" + height="13.833775" + x="289.03854" + y="167.14027" + ry="4.099587" /> + <text + fill="#000000" + fill-opacity="1" + stroke="none" + xml:space="preserve" + x="290.79922" + y="178.57553" + font-family="'Noto Sans'" + font-size="12.2761px" + font-weight="700" + font-style="normal" + id="text1468" + style="fill-rule:evenodd;stroke-width:0.992009;"><tspan + style="fill:#008000;" + id="tspan1466">RT</tspan></text> + </g> + <text + xml:space="preserve" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:16.5001px;line-height:1;font-family:Stanberry;-inkscape-font-specification:Stanberry;letter-spacing:0px;word-spacing:0px;fill:#0000ff;fill-opacity:1;stroke:none;stroke-width:0.332398;" + x="137.87466" + y="221.70712" + id="text1475"><tspan + sodipodi:role="line" + id="tspan1473" + x="137.87466" + y="221.70712" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:16.5001px;font-family:Stanberry;-inkscape-font-specification:Stanberry;fill:#0000ff;stroke-width:0.332398;">Reference</tspan><tspan + sodipodi:role="line" + x="137.87466" + y="238.20723" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:16.5001px;font-family:Stanberry;-inkscape-font-specification:Stanberry;fill:#0000ff;stroke-width:0.332398;" + id="tspan1589">Property</tspan></text> + <path + style="opacity:1;fill:none;stroke:#0000ff;stroke-width:0.839441;stroke-miterlimit:4;stroke-dasharray:none;marker-end:url(#marker1423);paint-order:markers fill stroke;stop-color:#000000;" + d="m 186.1229,210.28948 c 0.17712,-2.11062 7.35694,-8.61449 19.32363,-10.45333 11.96668,-1.83885 12.83929,-3.99319 19.85603,-15.53642" + id="path965" + sodipodi:nodetypes="czc" /> + <text + xml:space="preserve" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:16.5001px;line-height:1.25;font-family:Stanberry;-inkscape-font-specification:Stanberry;letter-spacing:0px;word-spacing:0px;fill:#0000ff;fill-opacity:1;stroke:none;stroke-width:0.332398;" + x="154.42661" + y="73.855499" + id="text1327"><tspan + sodipodi:role="line" + id="tspan1325" + x="154.42661" + y="73.855499" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:16.5001px;font-family:Stanberry;-inkscape-font-specification:Stanberry;fill:#0000ff;stroke-width:0.332398;">Inheritance (parent-child)</tspan></text> + <path + style="opacity:1;fill:none;stroke:#0000ff;stroke-width:0.839441;stroke-miterlimit:4;stroke-dasharray:none;marker-end:url(#Arrow2Lend);paint-order:markers fill stroke;stop-color:#000000;" + d="M 167.21,76.655873 C 152.19021,86.915727 140.56955,89.944957 126.44292,88.765028 114.95169,87.805221 104.43934,79.510241 95.374724,73.550088" + id="path1329" + sodipodi:nodetypes="cac" /> + <text + xml:space="preserve" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:16.5001px;line-height:1.25;font-family:Stanberry;-inkscape-font-specification:Stanberry;letter-spacing:0px;word-spacing:0px;fill:#0000ff;fill-opacity:1;stroke:none;stroke-width:0.332398;" + x="252.52292" + y="112.65959" + id="text1517"><tspan + sodipodi:role="line" + id="tspan1515" + x="252.52292" + y="112.65959" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:16.5001px;font-family:Stanberry;-inkscape-font-specification:Stanberry;fill:#0000ff;stroke-width:0.332398;">Properties</tspan></text> + <path + style="opacity:1;fill:none;stroke:#0000ff;stroke-width:0.839441;stroke-miterlimit:4;stroke-dasharray:none;marker-end:url(#marker1523);paint-order:markers fill stroke;stop-color:#000000;" + d="m 248.81133,110.33043 c -2.11061,0.17712 -22.14938,1.99358 -35.25122,12.5406 -13.10185,10.54701 -23.00529,13.62042 -41.71137,14.52454" + id="path1519" + sodipodi:nodetypes="czc" /> + <g + id="g4897" + transform="translate(0,-14.437559)"> + <g + fill="#ffffc0" + fill-opacity="1" + stroke="#ff0000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,288.811,165.733)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g204" + style="fill-rule:evenodd;stroke-width:1.24747;stroke-miterlimit:4;stroke-dasharray:none"> + <rect + vector-effect="" + x="0" + y="0" + width="151" + height="49.96944" + id="rect202" + style="stroke:#ff0000;stroke-width:1.24747;stroke-miterlimit:4;stroke-dasharray:none" /> + </g> + <g + fill="#ffffc0" + fill-opacity="1" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,288.811,165.733)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="700" + font-style="normal" + id="g210" + style="fill-rule:evenodd"> + <text + fill="#000000" + fill-opacity="1" + stroke="none" + xml:space="preserve" + x="49.5" + y="13.5" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="700" + font-style="normal" + id="text208">CellLine</text> + </g> + <g + fill="#ffffc0" + fill-opacity="1" + stroke="#ff0000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,288.811,165.733)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g214" + style="fill-rule:evenodd;stroke-width:1.24747;stroke-miterlimit:4;stroke-dasharray:none"> + <polyline + fill="none" + vector-effect="" + points="0,18 151,18 " + id="polyline212" + style="stroke:#ff0000;stroke-width:1.24747;stroke-miterlimit:4;stroke-dasharray:none" /> + </g> + <g + fill="#ffffc0" + fill-opacity="1" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,288.811,165.733)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g222" + style="fill-rule:evenodd"> + <text + fill="#000000" + fill-opacity="1" + stroke="none" + xml:space="preserve" + x="5" + y="31.5" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="text220">publication : Publication</text> + </g> + <g + id="g1446" + transform="translate(2.6045662,0.6156617)"> + <rect + style="opacity:1;fill:#d7f4d7;stroke:#008000;stroke-width:0.839788;paint-order:markers fill stroke;stop-color:#000000" + id="rect1441" + width="18.926079" + height="13.833775" + x="289.03854" + y="167.14027" + ry="4.099587" /> + <text + fill="#000000" + fill-opacity="1" + stroke="none" + xml:space="preserve" + x="290.79922" + y="178.57553" + font-family="'Noto Sans'" + font-size="12.2761px" + font-weight="700" + font-style="normal" + id="text208-3" + style="fill-rule:evenodd;stroke-width:0.992009"><tspan + style="fill:#008000" + id="tspan1426">RT</tspan></text> + </g> + <g + fill="#ffffc0" + fill-opacity="1" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,288.811,180.17055)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g4881" + style="fill-rule:evenodd"> + <text + fill="#000000" + fill-opacity="1" + stroke="none" + xml:space="preserve" + x="5" + y="31.5" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="text4879">note : TEXT</text> + </g> + </g> + </g> +</svg> diff --git a/src/doc/tutorials/img/simple_data_model.xmi b/src/doc/tutorials/img/simple_data_model.xmi new file mode 100644 index 0000000000000000000000000000000000000000..0f427ff3bb30666fe099351b6b16dd2b616fa765 --- /dev/null +++ b/src/doc/tutorials/img/simple_data_model.xmi @@ -0,0 +1,179 @@ +<?xml version="1.0" encoding="UTF-8"?> +<XMI verified="false" xmlns:UML="http://schema.omg.org/spec/UML/1.4" xmi.version="1.2" timestamp="2023-03-10T13:37:51"> + <XMI.header> + <XMI.documentation> + <XMI.exporter>umbrello uml modeller 2.32.2 http://umbrello.kde.org</XMI.exporter> + <XMI.exporterVersion>1.6.19</XMI.exporterVersion> + <XMI.exporterEncoding>UnicodeUTF8</XMI.exporterEncoding> + </XMI.documentation> + <XMI.metamodel xmi.name="UML" href="UML.xml" xmi.version="1.4"/> + </XMI.header> + <XMI.content> + <UML:Model isLeaf="false" isSpecification="false" isAbstract="false" xmi.id="m1" name="UML-Modell" isRoot="false"> + <UML:Namespace.ownedElement> + <UML:Stereotype isLeaf="false" isSpecification="false" isAbstract="false" xmi.id="folder" name="folder" isRoot="false" namespace="m1" visibility="public"/> + <UML:Stereotype isLeaf="false" isSpecification="false" isAbstract="false" xmi.id="upqb4wZPdsFug" name="W" isRoot="false" namespace="m1" visibility="public"/> + <UML:Model isLeaf="false" isSpecification="false" isAbstract="false" xmi.id="Logical_View" name="Logical View" isRoot="false" namespace="m1" visibility="public"> + <UML:Namespace.ownedElement> + <UML:Package isLeaf="false" isSpecification="false" isAbstract="false" xmi.id="Datatypes" name="Datatypes" isRoot="false" stereotype="folder" namespace="Logical_View" visibility="public"> + <UML:Namespace.ownedElement> + <UML:DataType isLeaf="false" isSpecification="false" isAbstract="false" xmi.id="uEHGpkgpZ5zSa" name="char" isRoot="false" namespace="Datatypes" visibility="public"/> + <UML:DataType isLeaf="false" isSpecification="false" isAbstract="false" xmi.id="uROhWD5rBLkN7" name="int" isRoot="false" namespace="Datatypes" visibility="public"/> + <UML:DataType isLeaf="false" isSpecification="false" isAbstract="false" xmi.id="uTEWbFbZg0H7d" name="float" isRoot="false" namespace="Datatypes" visibility="public"/> + <UML:DataType isLeaf="false" isSpecification="false" isAbstract="false" xmi.id="u63bynaVhcKPQ" name="double" isRoot="false" namespace="Datatypes" visibility="public"/> + <UML:DataType isLeaf="false" isSpecification="false" isAbstract="false" xmi.id="urNIscCDLp6YC" name="bool" isRoot="false" namespace="Datatypes" visibility="public"/> + <UML:DataType isLeaf="false" isSpecification="false" isAbstract="false" xmi.id="uEFDoBYDJxlwD" name="string" isRoot="false" namespace="Datatypes" visibility="public"/> + <UML:DataType isLeaf="false" isSpecification="false" isAbstract="false" xmi.id="uUN2gpYYSm0MQ" name="unsigned char" isRoot="false" namespace="Datatypes" visibility="public"/> + <UML:DataType isLeaf="false" isSpecification="false" isAbstract="false" xmi.id="uaOg7DtUmNa1O" name="signed char" isRoot="false" namespace="Datatypes" visibility="public"/> + <UML:DataType isLeaf="false" isSpecification="false" isAbstract="false" xmi.id="u2j8VdnwnD5No" name="unsigned int" isRoot="false" namespace="Datatypes" visibility="public"/> + <UML:DataType isLeaf="false" isSpecification="false" isAbstract="false" xmi.id="uCM9wQw4MCzTh" name="signed int" isRoot="false" namespace="Datatypes" visibility="public"/> + <UML:DataType isLeaf="false" isSpecification="false" isAbstract="false" xmi.id="uIiIYk08Eei43" name="short int" isRoot="false" namespace="Datatypes" visibility="public"/> + <UML:DataType isLeaf="false" isSpecification="false" isAbstract="false" xmi.id="utj7uju3Bcryx" name="unsigned short int" isRoot="false" namespace="Datatypes" visibility="public"/> + <UML:DataType isLeaf="false" isSpecification="false" isAbstract="false" xmi.id="u8CREq18tOkat" name="signed short int" isRoot="false" namespace="Datatypes" visibility="public"/> + <UML:DataType isLeaf="false" isSpecification="false" isAbstract="false" xmi.id="uq9KZiLh2fPSG" name="long int" isRoot="false" namespace="Datatypes" visibility="public"/> + <UML:DataType isLeaf="false" isSpecification="false" isAbstract="false" xmi.id="ul1JcLwsggvUT" name="signed long int" isRoot="false" namespace="Datatypes" visibility="public"/> + <UML:DataType isLeaf="false" isSpecification="false" isAbstract="false" xmi.id="urLFa80H4C1WH" name="unsigned long int" isRoot="false" namespace="Datatypes" visibility="public"/> + <UML:DataType isLeaf="false" isSpecification="false" isAbstract="false" xmi.id="u2WVtsIOXYKOE" name="long double" isRoot="false" namespace="Datatypes" visibility="public"/> + <UML:DataType isLeaf="false" isSpecification="false" isAbstract="false" xmi.id="u6fN7PqC7hndQ" name="wchar_t" isRoot="false" namespace="Datatypes" visibility="public"/> + </UML:Namespace.ownedElement> + </UML:Package> + <UML:Class isLeaf="false" isSpecification="false" isAbstract="false" xmi.id="uvUQiWatyXkSJ" name="Experiment" isRoot="false" namespace="Logical_View" visibility="public"> + <UML:Classifier.feature> + <UML:Attribute type="uefR9qLrX0KbN" isSpecification="false" xmi.id="uSJY4vfmi5BN4" name="experimenter" visibility="public"/> + <UML:Attribute type="u0du3dSxselXO" isSpecification="false" xmi.id="uFqEtWMUa5mCe" name="lab_notes" visibility="private"/> + </UML:Classifier.feature> + </UML:Class> + <UML:Class isLeaf="false" isSpecification="false" isAbstract="false" xmi.id="uefR9qLrX0KbN" name="TEXT" isRoot="false" namespace="Logical_View" visibility="public"/> + <UML:Class isLeaf="false" isSpecification="false" isAbstract="false" xmi.id="u0du3dSxselXO" name="FILE" isRoot="false" namespace="Logical_View" visibility="public"/> + <UML:Class isLeaf="false" isSpecification="false" isAbstract="false" xmi.id="uAFk8Yy0StoYm" name="CellCultureExperiment" isRoot="false" namespace="Logical_View" visibility="public"> + <UML:GeneralizableElement.generalization> + <UML:Generalization xmi.idref="u8oRooFrsWVhk"/> + </UML:GeneralizableElement.generalization> + <UML:Classifier.feature> + <UML:Attribute type="uefR9qLrX0KbN" isSpecification="false" xmi.id="uNpTs7ekpuH4I" name="experimenter" visibility="public"/> + <UML:Attribute type="u0du3dSxselXO" isSpecification="false" xmi.id="uL8s65ael2sWY" name="lab_notes" visibility="private"/> + <UML:Attribute type="ur1GkbdOwkjef" isSpecification="false" xmi.id="um6gksRKWydK1" name="number_of_dishes" visibility="public"/> + <UML:Attribute type="upcSaMWLBkK65" isSpecification="false" xmi.id="uVKvnnqgPsRga" name="cell_line" visibility="public"/> + </UML:Classifier.feature> + </UML:Class> + <UML:Class isLeaf="false" isSpecification="false" isAbstract="false" xmi.id="ur1GkbdOwkjef" name="INTEGER" isRoot="false" namespace="Logical_View" visibility="public"/> + <UML:Generalization parent="uvUQiWatyXkSJ" isSpecification="false" discriminator="" xmi.id="u8oRooFrsWVhk" child="uAFk8Yy0StoYm" name="" namespace="Logical_View" visibility="public"/> + <UML:Class isLeaf="false" isSpecification="false" isAbstract="false" xmi.id="upcSaMWLBkK65" name="CellLine" isRoot="false" namespace="Logical_View" visibility="public"> + <UML:Classifier.feature> + <UML:Attribute type="uO0C1L9PSx1pt" isSpecification="false" xmi.id="utENdz0EM98oE" name="publication" visibility="public"/> + </UML:Classifier.feature> + </UML:Class> + <UML:Class isLeaf="false" isSpecification="false" isAbstract="false" xmi.id="uO0C1L9PSx1pt" name="Publication" isRoot="false" namespace="Logical_View" visibility="public"> + <UML:Classifier.feature> + <UML:Attribute type="uefR9qLrX0KbN" isSpecification="false" xmi.id="uBD7YB7ALmZTE" name="author" visibility="private"/> + </UML:Classifier.feature> + </UML:Class> + </UML:Namespace.ownedElement> + <XMI.extension xmi.extender="umbrello"> + <diagrams resolution="99"> + <diagram showscope="1" showstereotype="1" isopen="1" showpackage="1" textcolor="#000000" canvasheight="284.288" linecolor="#ff0000" canvaswidth="441.137" documentation="" backgroundcolor="#fcfcfc" xmi.id="u89npYrdhZ5hM" showopsig="1" showattribassocs="1" localid="-1" griddotcolor="#eff0f1" zoom="152.08749999999995" usefillcolor="1" showops="1" linewidth="0" name="Klassendiagramm" showatts="1" showpubliconly="0" fillcolor="#ffffc0" font="Noto Sans,9,-1,5,50,0,0,0,0,0,Regular" type="1" snapcsgrid="0" snapy="25" snapx="25" showgrid="0" snapgrid="0" showattsig="1"> + <widgets> + <classwidget showscope="0" showopsigs="602" showstereotype="1" showpackage="1" textcolor="#000000" linecolor="#ff0000" xmi.id="uvUQiWatyXkSJ" usesdiagramusefillcolor="0" showattsigs="602" localid="unUcCTIMGl5Co" usesdiagramfillcolor="0" isinstance="0" usefillcolor="1" linewidth="0" showpubliconly="0" fillcolor="#ffffc0" font="Noto Sans,9,-1,5,50,0,0,0,0,0,Regular" width="135" height="54" showoperations="0" showattributes="1" x="-135.137" y="-456.315" autoresize="1"/> + <classwidget showscope="0" showopsigs="602" showstereotype="1" showpackage="1" textcolor="#000000" linecolor="#ff0000" xmi.id="upcSaMWLBkK65" usesdiagramusefillcolor="0" showattsigs="602" localid="ux9toAI1KGWvY" usesdiagramfillcolor="0" isinstance="0" usefillcolor="1" linewidth="0" showpubliconly="0" fillcolor="#ffffc0" font="Noto Sans,9,-1,5,50,0,0,0,0,0,Regular" width="151" height="36" showoperations="0" showattributes="1" x="131.37" y="-290.247" autoresize="1"/> + <classwidget showscope="0" showopsigs="602" showstereotype="1" showpackage="1" textcolor="#000000" linecolor="#ff0000" xmi.id="uAFk8Yy0StoYm" usesdiagramusefillcolor="0" showattsigs="602" localid="uCtngsW0eC2Nm" usesdiagramfillcolor="0" isinstance="0" usefillcolor="1" linewidth="0" showpubliconly="0" fillcolor="#ffffc0" font="Noto Sans,9,-1,5,50,0,0,0,0,0,Regular" width="188" height="90" showoperations="0" showattributes="1" x="-158.767" y="-363.013" autoresize="1"/> + <classwidget showscope="0" showopsigs="602" showstereotype="1" showpackage="1" textcolor="#000000" linecolor="#ff0000" xmi.id="uO0C1L9PSx1pt" usesdiagramusefillcolor="0" showattsigs="602" localid="udxtFf6VgSeNQ" usesdiagramfillcolor="0" isinstance="0" usefillcolor="1" linewidth="0" showpubliconly="0" fillcolor="#ffffc0" font="Noto Sans,9,-1,5,50,0,0,0,0,0,Regular" width="92" height="36" showoperations="0" showattributes="1" x="86.1255" y="-208.027" autoresize="1"/> + </widgets> + <messages/> + <associations> + <assocwidget totalcountb="2" widgetaid="uAFk8Yy0StoYm" totalcounta="2" indexb="1" textcolor="none" linecolor="#ff0000" xmi.id="u8oRooFrsWVhk" usesdiagramusefillcolor="1" localid="uP6sMUUIUwAdw" seqnum="" usesdiagramfillcolor="1" usefillcolor="1" linewidth="0" indexa="1" fillcolor="none" font="Noto Sans,9,-1,5,50,0,0,0,0,0,Regular" widgetbid="uvUQiWatyXkSJ" type="500" autoresize="1"> + <linepath layout="Direct"> + <startpoint startx="-68.7281" starty="-363.013"/> + <endpoint endx="-68.7281" endy="-402.315"/> + </linepath> + </assocwidget> + <assocwidget totalcountb="2" widgetaid="uAFk8Yy0StoYm" totalcounta="2" indexb="1" textcolor="none" linecolor="#ff0000" xmi.id="uVKvnnqgPsRga" usesdiagramusefillcolor="1" localid="ulyMsjM0nbs3a" seqnum="" changeabilityA="900" usesdiagramfillcolor="1" visibilityA="0" usefillcolor="1" linewidth="0" indexa="1" fillcolor="none" font="Noto Sans,9,-1,5,50,0,0,0,0,0,Regular" changeabilityB="900" widgetbid="upcSaMWLBkK65" type="510" autoresize="1" visibilityB="0"> + <linepath layout="Direct"> + <startpoint startx="29.2327" starty="-277.754"/> + <endpoint endx="131.37" endy="-277.754"/> + </linepath> + <floatingtext showstereotype="1" textcolor="none" linecolor="#ff0000" xmi.id="uVGPB1NJvP8wt" usesdiagramusefillcolor="1" localid="uckP5Ky3TvfkT" pretext="+" usesdiagramfillcolor="1" text="cell_line" isinstance="0" usefillcolor="1" linewidth="0" fillcolor="none" font="Noto Sans,9,-1,5,50,0,0,0,0,0,Regular" width="63" posttext="" height="22" x="70" y="-284" autoresize="1" role="710"/> + </assocwidget> + <assocwidget totalcountb="2" widgetaid="upcSaMWLBkK65" totalcounta="2" indexb="1" textcolor="none" linecolor="#ff0000" xmi.id="utENdz0EM98oE" usesdiagramusefillcolor="1" localid="u8A6XeV5sThX3" seqnum="" changeabilityA="900" usesdiagramfillcolor="1" visibilityA="0" usefillcolor="1" linewidth="0" indexa="1" fillcolor="none" font="Noto Sans,9,-1,5,50,0,0,0,0,0,Regular" changeabilityB="900" widgetbid="uO0C1L9PSx1pt" type="510" autoresize="1" visibilityB="0"> + <linepath layout="Direct"> + <startpoint startx="162.345" starty="-254.247"/> + <endpoint endx="162.345" endy="-208.027"/> + </linepath> + <floatingtext showstereotype="1" textcolor="none" linecolor="#ff0000" xmi.id="ufrw1SbOcMWr9" usesdiagramusefillcolor="1" localid="uY15jzXWQtNEA" pretext="+" usesdiagramfillcolor="1" text="publication" isinstance="0" usefillcolor="1" linewidth="0" fillcolor="none" font="Noto Sans,9,-1,5,50,0,0,0,0,0,Regular" width="81" posttext="" height="22" x="103" y="-235" autoresize="1" role="710"/> + </assocwidget> + </associations> + </diagram> + </diagrams> + </XMI.extension> + </UML:Model> + <UML:Model isLeaf="false" isSpecification="false" isAbstract="false" xmi.id="Use_Case_View" name="Use Case View" isRoot="false" namespace="m1" visibility="public"> + <UML:Namespace.ownedElement/> + </UML:Model> + <UML:Model isLeaf="false" isSpecification="false" isAbstract="false" xmi.id="Component_View" name="Component View" isRoot="false" namespace="m1" visibility="public"> + <UML:Namespace.ownedElement/> + </UML:Model> + <UML:Model isLeaf="false" isSpecification="false" isAbstract="false" xmi.id="Deployment_View" name="Deployment View" isRoot="false" namespace="m1" visibility="public"> + <UML:Namespace.ownedElement/> + </UML:Model> + <UML:Model isLeaf="false" isSpecification="false" isAbstract="false" xmi.id="Entity_Relationship_Model" name="Entity Relationship Model" isRoot="false" namespace="m1" visibility="public"> + <UML:Namespace.ownedElement/> + </UML:Model> + </UML:Namespace.ownedElement> + </UML:Model> + </XMI.content> + <XMI.extensions xmi.extender="umbrello"> + <docsettings viewid="u89npYrdhZ5hM" documentation="" uniqueid="u1Nwn5m62fx8p"/> + <listview> + <listitem id="Views" type="800" open="1"> + <listitem id="Use_Case_View" type="802" open="1"/> + <listitem id="Entity_Relationship_Model" type="836" open="1"/> + <listitem id="Component_View" type="821" open="1"/> + <listitem id="Logical_View" type="801" open="1"> + <listitem id="uAFk8Yy0StoYm" type="813" open="1"> + <listitem id="uVKvnnqgPsRga" type="814" open="0"/> + <listitem id="uNpTs7ekpuH4I" type="814" open="0"/> + <listitem id="uL8s65ael2sWY" type="814" open="0"/> + <listitem id="um6gksRKWydK1" type="814" open="0"/> + </listitem> + <listitem id="upcSaMWLBkK65" type="813" open="1"> + <listitem id="utENdz0EM98oE" type="814" open="0"/> + </listitem> + <listitem id="Datatypes" type="830" open="0"> + <listitem id="urNIscCDLp6YC" type="829" open="0"/> + <listitem id="uEHGpkgpZ5zSa" type="829" open="0"/> + <listitem id="u63bynaVhcKPQ" type="829" open="0"/> + <listitem id="uTEWbFbZg0H7d" type="829" open="0"/> + <listitem id="uROhWD5rBLkN7" type="829" open="0"/> + <listitem id="u2WVtsIOXYKOE" type="829" open="0"/> + <listitem id="uq9KZiLh2fPSG" type="829" open="0"/> + <listitem id="uIiIYk08Eei43" type="829" open="0"/> + <listitem id="uaOg7DtUmNa1O" type="829" open="0"/> + <listitem id="uCM9wQw4MCzTh" type="829" open="0"/> + <listitem id="ul1JcLwsggvUT" type="829" open="0"/> + <listitem id="u8CREq18tOkat" type="829" open="0"/> + <listitem id="uEFDoBYDJxlwD" type="829" open="0"/> + <listitem id="uUN2gpYYSm0MQ" type="829" open="0"/> + <listitem id="u2j8VdnwnD5No" type="829" open="0"/> + <listitem id="urLFa80H4C1WH" type="829" open="0"/> + <listitem id="utj7uju3Bcryx" type="829" open="0"/> + <listitem id="u6fN7PqC7hndQ" type="829" open="0"/> + </listitem> + <listitem id="uvUQiWatyXkSJ" type="813" open="1"> + <listitem id="uSJY4vfmi5BN4" type="814" open="0"/> + <listitem id="uFqEtWMUa5mCe" type="814" open="0"/> + </listitem> + <listitem id="u0du3dSxselXO" type="813" open="1"/> + <listitem id="ur1GkbdOwkjef" type="813" open="1"/> + <listitem id="u89npYrdhZ5hM" type="807" label="Klassendiagramm" open="0"/> + <listitem id="uO0C1L9PSx1pt" type="813" open="1"> + <listitem id="uBD7YB7ALmZTE" type="814" open="0"/> + </listitem> + <listitem id="uefR9qLrX0KbN" type="813" open="1"/> + </listitem> + <listitem id="Deployment_View" type="827" open="1"/> + </listitem> + </listview> + <codegeneration> + <codegenerator language="C++"/> + </codegeneration> + </XMI.extensions> +</XMI> diff --git a/src/doc/tutorials/img/very_simple_data_model.png b/src/doc/tutorials/img/very_simple_data_model.png new file mode 100644 index 0000000000000000000000000000000000000000..793767b95b869bf2bfb20315bd9023a1b486bac6 Binary files /dev/null and b/src/doc/tutorials/img/very_simple_data_model.png differ diff --git a/src/doc/tutorials/img/very_simple_data_model.svg b/src/doc/tutorials/img/very_simple_data_model.svg new file mode 100644 index 0000000000000000000000000000000000000000..ca1b6f923ad32c04a3302a02f16c782531e002f4 --- /dev/null +++ b/src/doc/tutorials/img/very_simple_data_model.svg @@ -0,0 +1,267 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="56.61758mm" + height="20.597263mm" + viewBox="0 0 220.67571 80.280722" + version="1.2" + id="svg236" + sodipodi:docname="very_simple_data_model.svg" + inkscape:version="1.0.2 (e86c870879, 2021-01-15)" + inkscape:export-filename="/home/daniel/indiscale/management/external/bmpg/management/documentation/data_model/how_to_datamodel/very_simple_data_model.png" + inkscape:export-xdpi="897.25" + inkscape:export-ydpi="897.25"> + <metadata + id="metadata240"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title>Qt SVG Document</dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1920" + inkscape:window-height="1135" + id="namedview238" + showgrid="false" + inkscape:zoom="3.6046958" + inkscape:cx="160.39549" + inkscape:cy="4.7271483" + inkscape:window-x="0" + inkscape:window-y="0" + inkscape:window-maximized="1" + inkscape:current-layer="layer1" + inkscape:document-rotation="0" /> + <title + id="title2">Qt SVG Document</title> + <desc + id="desc4">Generated with Qt</desc> + <defs + id="defs6"> + <marker + style="overflow:visible" + id="marker1523" + refX="0" + refY="0" + orient="auto" + inkscape:stockid="Arrow2Lend" + inkscape:isstock="true"> + <path + transform="matrix(-1.1,0,0,-1.1,-1.1,0)" + d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z" + style="fill:#0000ff;fill-opacity:1;fill-rule:evenodd;stroke:#0000ff;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1" + id="path1521" /> + </marker> + <marker + style="overflow:visible" + id="marker1333" + refX="0" + refY="0" + orient="auto" + inkscape:stockid="Arrow2Mend" + inkscape:isstock="true"> + <path + transform="scale(-0.6)" + d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z" + style="fill:#0000ff;fill-opacity:1;fill-rule:evenodd;stroke:#0000ff;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1" + id="path1331" /> + </marker> + <marker + style="overflow:visible" + id="Arrow1Mend" + refX="0" + refY="0" + orient="auto" + inkscape:stockid="Arrow1Mend" + inkscape:isstock="true"> + <path + transform="matrix(-0.4,0,0,-0.4,-4,0)" + style="fill:#0000ff;fill-opacity:1;fill-rule:evenodd;stroke:#0000ff;stroke-width:1pt;stroke-opacity:1" + d="M 0,0 5,-5 -12.5,0 5,5 Z" + id="path976" /> + </marker> + </defs> + <g + inkscape:groupmode="layer" + id="layer1" + inkscape:label="Layer 1" + transform="translate(-88.927441,-46.784756)"> + <g + id="g1749" + transform="translate(150.56113,71.817619)"> + <rect + x="24.4333" + y="0.99200898" + width="133.92122" + height="53.568485" + id="rect132" + style="font-style:normal;font-weight:400;font-size:12.375px;font-family:'Noto Sans';fill:#ffffc0;fill-opacity:1;fill-rule:evenodd;stroke:#ff0000;stroke-width:1.37501;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <g + fill="#ffffc0" + fill-opacity="1" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,24.4333,0.992009)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="700" + font-style="normal" + id="g140" + style="fill-rule:evenodd"> + <text + fill="#000000" + fill-opacity="1" + stroke="none" + xml:space="preserve" + x="30.5" + y="13.5" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="700" + font-style="normal" + id="text138">Experiment</text> + </g> + <g + fill="#ffffc0" + fill-opacity="1" + stroke="#ff0000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,24.4333,0.992009)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g144" + style="fill-rule:evenodd;stroke-width:1.38608;stroke-miterlimit:4;stroke-dasharray:none"> + <polyline + fill="none" + points="0,18 135,18 " + id="polyline142" + style="stroke-width:1.38608;stroke-miterlimit:4;stroke-dasharray:none" /> + </g> + <g + fill="#ffffc0" + fill-opacity="1" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,24.4333,0.992009)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g152" + style="fill-rule:evenodd"> + <text + fill="#000000" + fill-opacity="1" + stroke="none" + xml:space="preserve" + x="5" + y="31.5" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="text150">experimenter : TEXT</text> + </g> + <g + fill="#ffffc0" + fill-opacity="1" + stroke="#000000" + stroke-opacity="1" + stroke-width="1" + stroke-linecap="square" + stroke-linejoin="bevel" + transform="matrix(0.992009,0,0,0.992009,24.4333,0.992009)" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="g158" + style="fill-rule:evenodd"> + <text + fill="#000000" + fill-opacity="1" + stroke="none" + xml:space="preserve" + x="5" + y="49.5" + font-family="'Noto Sans'" + font-size="12.375px" + font-weight="400" + font-style="normal" + id="text156">lab_notes : FILE</text> + </g> + <g + id="g1470" + transform="translate(-261.77311,-164.12532)"> + <rect + style="opacity:1;fill:#d7f4d7;stroke:#008000;stroke-width:0.839788;paint-order:markers fill stroke;stop-color:#000000" + id="rect1464" + width="18.926079" + height="13.833775" + x="289.03854" + y="167.14027" + ry="4.099587" /> + <text + fill="#000000" + fill-opacity="1" + stroke="none" + xml:space="preserve" + x="290.79922" + y="178.57553" + font-family="'Noto Sans'" + font-size="12.2761px" + font-weight="700" + font-style="normal" + id="text1468" + style="fill-rule:evenodd;stroke-width:0.992009"><tspan + style="fill:#008000" + id="tspan1466">RT</tspan></text> + </g> + </g> + <text + xml:space="preserve" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:16.5001px;line-height:1.25;font-family:Stanberry;-inkscape-font-specification:Stanberry;letter-spacing:0px;word-spacing:0px;fill:#0000ff;fill-opacity:1;stroke:none;stroke-width:0.332398" + x="88.282906" + y="58.603783" + id="text1517"><tspan + sodipodi:role="line" + id="tspan1515" + x="88.282906" + y="58.603783" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:16.5001px;font-family:Stanberry;-inkscape-font-specification:Stanberry;fill:#0000ff;stroke-width:0.332398">RecordType</tspan></text> + <path + style="opacity:1;fill:none;stroke:#0000ff;stroke-width:0.839441;stroke-miterlimit:4;stroke-dasharray:none;marker-end:url(#marker1523);paint-order:markers fill stroke;stop-color:#000000" + d="m 104.62524,64.323266 c 1.42777,5.850954 10.16944,13.595229 20.72552,16.588753 15.03521,4.263734 24.20804,-2.690657 44.43576,-0.92334" + id="path1519" + sodipodi:nodetypes="csc" /> + </g> +</svg> diff --git a/src/doc/tutorials/statemachine.rst b/src/doc/tutorials/statemachine.rst index 317620423e6221ccaf29297fc1f71f0c008f78a0..dc46885a39548042314e77c76b1578e1a8ac2a74 100644 --- a/src/doc/tutorials/statemachine.rst +++ b/src/doc/tutorials/statemachine.rst @@ -30,5 +30,5 @@ of Records that have that parent. For example by executing: .. code-block:: Python rt = db.RecordType("Article").retrieve() - rt.state = db.State(name="UnPublished", model="Publish Life-cycle")`` + rt.state = db.State(name="UnPublished", model="Publish Life-cycle") rt.update() diff --git a/src/main/java/org/caosdb/datetime/FragmentDateTime.java b/src/main/java/org/caosdb/datetime/FragmentDateTime.java index 38c16a988a0103a121767b99078c0ca41c6263d5..78ef7199970ac3aa52a53cae96744cd90fb23c20 100644 --- a/src/main/java/org/caosdb/datetime/FragmentDateTime.java +++ b/src/main/java/org/caosdb/datetime/FragmentDateTime.java @@ -21,7 +21,9 @@ * ** end header */ -/** @review Daniel Hornung 2022-03-04 */ +/** + * @review Daniel Hornung 2022-03-04 + */ package org.caosdb.datetime; import java.util.Objects; diff --git a/src/main/java/org/caosdb/datetime/UTCDateTime.java b/src/main/java/org/caosdb/datetime/UTCDateTime.java index 1dd86cc975d4b92066f2cf44fff51493ade5babe..a7719d5a3f30d862be0d7646796a4f2800149b06 100644 --- a/src/main/java/org/caosdb/datetime/UTCDateTime.java +++ b/src/main/java/org/caosdb/datetime/UTCDateTime.java @@ -21,7 +21,9 @@ * ** end header */ -/** @review Daniel Hornung 2022-03-04 */ +/** + * @review Daniel Hornung 2022-03-04 + */ package org.caosdb.datetime; import java.util.ArrayList; diff --git a/src/main/java/org/caosdb/server/CaosDBServer.java b/src/main/java/org/caosdb/server/CaosDBServer.java index 0c95e22dd1ca6b0d9e00a69784c5584386bfb39b..b99d63890d0cedac34af6f0e5e7557e48c0f2efa 100644 --- a/src/main/java/org/caosdb/server/CaosDBServer.java +++ b/src/main/java/org/caosdb/server/CaosDBServer.java @@ -3,8 +3,8 @@ * * Copyright (C) 2018 Research Group Biomedical Physics, * Max-Planck-Institute for Dynamics and Self-Organization Göttingen - * Copyright (C) 2019-2021 IndiScale GmbH <info@indiscale.com> - * Copyright (C) 2019-2021 Timm Fitschen <t.fitschen@indiscale.com> + * Copyright (C) 2019-2021,2023 IndiScale GmbH <info@indiscale.com> + * Copyright (C) 2019-2021,2023 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 @@ -73,7 +73,6 @@ import org.caosdb.server.resource.LogoutResource; import org.caosdb.server.resource.PermissionRulesResource; import org.caosdb.server.resource.RolesResource; import org.caosdb.server.resource.ScriptingResource; -import org.caosdb.server.resource.ServerLogsResource; import org.caosdb.server.resource.ServerPropertiesResource; import org.caosdb.server.resource.SharedFileResource; import org.caosdb.server.resource.UserResource; @@ -106,6 +105,7 @@ import org.restlet.data.Reference; import org.restlet.data.ServerInfo; import org.restlet.data.Status; import org.restlet.engine.Engine; +import org.restlet.routing.Redirector; import org.restlet.routing.Route; import org.restlet.routing.Router; import org.restlet.routing.Template; @@ -126,6 +126,7 @@ public class CaosDBServer extends Application { 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 boolean USE_CACHE = true; private static Scheduler SCHEDULER; public static String getServerProperty(final String key) { @@ -150,6 +151,7 @@ public class CaosDBServer extends Application { parseArguments(args); initScheduler(); initServerProperties(); + initCaching(); initTimeZone(); initOneTimeTokens(); initShiro(); @@ -195,6 +197,11 @@ public class CaosDBServer extends Application { SERVER_PROPERTIES = ServerProperties.initServerProperties(); } + public static void initCaching() { + USE_CACHE = + !Boolean.parseBoolean(CaosDBServer.getServerProperty(ServerProperties.KEY_CACHE_DISABLE)); + } + /** * Precedence order: * @@ -658,7 +665,7 @@ public class CaosDBServer extends Application { return 0; } return -1; - }; + } @Override public int match(final String formattedString) { @@ -703,6 +710,9 @@ public class CaosDBServer extends Application { protectedRouter.attach("/EntityPermissions/", EntityPermissionsResource.class); protectedRouter.attach("/EntityPermissions/{specifier}", EntityPermissionsResource.class); protectedRouter.attach("/Owner/{specifier}", EntityOwnerResource.class); + protectedRouter.attach( + "/FileSystem", + new Redirector(getContext(), "/FileSystem/", Redirector.MODE_CLIENT_PERMANENT)); protectedRouter.attach("/FileSystem/", FileSystemResource.class); // FileSystem etc. needs to accept parameters which contain slashes and would otherwise be // split at the first separator @@ -725,12 +735,6 @@ public class CaosDBServer extends Application { protectedRouter.attach("/Role/{specifier}", RolesResource.class); protectedRouter.attach("/PermissionRules/{specifier}", PermissionRulesResource.class); protectedRouter.attach("/PermissionRules/{realm}/{specifier}", PermissionRulesResource.class); - protectedRouter - .attach("/ServerLogs/", ServerLogsResource.class) - .setMatchingMode(Template.MODE_STARTS_WITH); - protectedRouter - .attach("/ServerLogs", ServerLogsResource.class) - .setMatchingMode(Template.MODE_STARTS_WITH); protectedRouter.attach("/login?username={username}", AuthenticationResource.class); protectedRouter.attach("/logout", LogoutResource.class); protectedRouter.attach("/_server_properties", ServerPropertiesResource.class); @@ -872,6 +876,10 @@ public class CaosDBServer extends Application { throws SchedulerException { SCHEDULER.scheduleJob(job, trigger); } + + public static boolean useCache() { + return USE_CACHE; + } } class CaosDBComponent extends Component { @@ -883,6 +891,13 @@ class CaosDBComponent extends Component { public CaosDBComponent() { super(); + String responseLogFormat = + CaosDBServer.getServerProperty(ServerProperties.KEY_REST_RESPONSE_LOG_FORMAT); + if ("OFF".equalsIgnoreCase(responseLogFormat)) { + getLogService().setEnabled(false); + } else if (responseLogFormat != null && !responseLogFormat.isEmpty()) { + getLogService().setResponseLogFormat(responseLogFormat); + } setName(CaosDBServer.getServerProperty(ServerProperties.KEY_SERVER_NAME)); setOwner(CaosDBServer.getServerProperty(ServerProperties.KEY_SERVER_OWNER)); } diff --git a/src/main/java/org/caosdb/server/ServerProperties.java b/src/main/java/org/caosdb/server/ServerProperties.java index 3027940e5c8926d5c2b6bd64430e52b42f8685e0..6e283697de198652afeabae98430de0b9ec9c32a 100644 --- a/src/main/java/org/caosdb/server/ServerProperties.java +++ b/src/main/java/org/caosdb/server/ServerProperties.java @@ -137,6 +137,8 @@ public class ServerProperties extends Properties implements Observable { public static final String KEY_PASSWORD_STRENGTH_REGEX = "PASSWORD_VALID_REGEX"; public static final String KEY_PASSWORD_WEAK_MESSAGE = "PASSWORD_INVALID_MESSAGE"; + public static final String KEY_REST_RESPONSE_LOG_FORMAT = "REST_RESPONSE_LOG_FORMAT"; + public static final String KEY_GRPC_RESPONSE_LOG_FORMAT = "GRPC_RESPONSE_LOG_FORMAT"; /** * Read the config files and initialize the server properties. diff --git a/src/main/java/org/caosdb/server/accessControl/Pam.java b/src/main/java/org/caosdb/server/accessControl/Pam.java index 992665c1db3ef42d3770c71e89fa5444f43f9d95..76fde331dc97b51ed82083999e4d49b94176695d 100644 --- a/src/main/java/org/caosdb/server/accessControl/Pam.java +++ b/src/main/java/org/caosdb/server/accessControl/Pam.java @@ -145,7 +145,9 @@ public class Pam implements UserSource { return this.map; } - /** @see {@link UserSource#resolveRolesForUsername(String)} */ + /** + * @see {@link UserSource#resolveRolesForUsername(String)} + */ @Override public Set<String> resolveRolesForUsername(final String username) { final Set<String> resulting_roles = new HashSet<String>(); @@ -266,7 +268,9 @@ public class Pam implements UserSource { return username != null && isIncorporated(username); } - /** @see {@link UserSource#isValid(String, String)}. */ + /** + * @see {@link UserSource#isValid(String, String)}. + */ @Override public boolean isValid(final String username, final String password) { if (username != null && isIncorporated(username)) { diff --git a/src/main/java/org/caosdb/server/caching/JCSCacheHelper.java b/src/main/java/org/caosdb/server/caching/JCSCacheHelper.java index 1613b8be8194425ab14e5e68b9215a0ba92be8d3..f294e4bb029b7e03d2864ae832b3044685536404 100644 --- a/src/main/java/org/caosdb/server/caching/JCSCacheHelper.java +++ b/src/main/java/org/caosdb/server/caching/JCSCacheHelper.java @@ -62,8 +62,7 @@ public class JCSCacheHelper implements CacheHelper { } public static void init() { - final boolean disabled = - Boolean.parseBoolean(CaosDBServer.getServerProperty(ServerProperties.KEY_CACHE_DISABLE)); + final boolean disabled = !CaosDBServer.useCache(); init(CaosDBServer.getServerProperty(ServerProperties.KEY_CACHE_CONF_LOC), disabled); } diff --git a/src/main/java/org/caosdb/server/database/BackendTransaction.java b/src/main/java/org/caosdb/server/database/BackendTransaction.java index c4af8f462c3ece4b06e623bebc2d049433a36322..4ac6799878cdc89b4136a4499159bfe234c33443 100644 --- a/src/main/java/org/caosdb/server/database/BackendTransaction.java +++ b/src/main/java/org/caosdb/server/database/BackendTransaction.java @@ -3,8 +3,8 @@ * * Copyright (C) 2018 Research Group Biomedical Physics, * Max-Planck-Institute for Dynamics and Self-Organization Göttingen - * Copyright (C) 2019-2021 IndiScale GmbH <info@indiscale.com> - * Copyright (C) 2019-2021 Timm Fitschen <t.fitschen@indiscale.com> + * Copyright (C) 2019-2023 IndiScale GmbH <info@indiscale.com> + * Copyright (C) 2019-2023 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 @@ -29,7 +29,6 @@ import org.caosdb.server.database.backend.implementation.MySQL.MySQLDeleteRole; import org.caosdb.server.database.backend.implementation.MySQL.MySQLDeleteSparseEntity; import org.caosdb.server.database.backend.implementation.MySQL.MySQLDeleteUser; import org.caosdb.server.database.backend.implementation.MySQL.MySQLGetAllNames; -import org.caosdb.server.database.backend.implementation.MySQL.MySQLGetChildren; import org.caosdb.server.database.backend.implementation.MySQL.MySQLGetDependentEntities; import org.caosdb.server.database.backend.implementation.MySQL.MySQLGetFileEntityByPath; import org.caosdb.server.database.backend.implementation.MySQL.MySQLGetFilesInDirectory; @@ -41,7 +40,6 @@ import org.caosdb.server.database.backend.implementation.MySQL.MySQLInsertEntity import org.caosdb.server.database.backend.implementation.MySQL.MySQLInsertEntityProperties; import org.caosdb.server.database.backend.implementation.MySQL.MySQLInsertFSODescriptor; import org.caosdb.server.database.backend.implementation.MySQL.MySQLInsertLinCon; -import org.caosdb.server.database.backend.implementation.MySQL.MySQLInsertLogRecord; import org.caosdb.server.database.backend.implementation.MySQL.MySQLInsertParents; import org.caosdb.server.database.backend.implementation.MySQL.MySQLInsertRole; import org.caosdb.server.database.backend.implementation.MySQL.MySQLInsertSparseEntity; @@ -51,11 +49,10 @@ import org.caosdb.server.database.backend.implementation.MySQL.MySQLListFiles; import org.caosdb.server.database.backend.implementation.MySQL.MySQLListRoles; import org.caosdb.server.database.backend.implementation.MySQL.MySQLListUsers; import org.caosdb.server.database.backend.implementation.MySQL.MySQLLogUserVisit; -import org.caosdb.server.database.backend.implementation.MySQL.MySQLRegisterSubDomain; import org.caosdb.server.database.backend.implementation.MySQL.MySQLRetrieveAll; +import org.caosdb.server.database.backend.implementation.MySQL.MySQLRetrieveCurrentMaxId; import org.caosdb.server.database.backend.implementation.MySQL.MySQLRetrieveDatatypes; import org.caosdb.server.database.backend.implementation.MySQL.MySQLRetrieveEntityACL; -import org.caosdb.server.database.backend.implementation.MySQL.MySQLRetrieveLogRecord; import org.caosdb.server.database.backend.implementation.MySQL.MySQLRetrieveParents; import org.caosdb.server.database.backend.implementation.MySQL.MySQLRetrievePasswordValidator; import org.caosdb.server.database.backend.implementation.MySQL.MySQLRetrievePermissionRules; @@ -81,7 +78,6 @@ import org.caosdb.server.database.backend.interfaces.DeleteRoleImpl; import org.caosdb.server.database.backend.interfaces.DeleteSparseEntityImpl; import org.caosdb.server.database.backend.interfaces.DeleteUserImpl; import org.caosdb.server.database.backend.interfaces.GetAllNamesImpl; -import org.caosdb.server.database.backend.interfaces.GetChildrenImpl; import org.caosdb.server.database.backend.interfaces.GetDependentEntitiesImpl; import org.caosdb.server.database.backend.interfaces.GetFileEntityByPathImpl; import org.caosdb.server.database.backend.interfaces.GetFilesInDirectoryImpl; @@ -93,7 +89,6 @@ import org.caosdb.server.database.backend.interfaces.InsertEntityDatatypeImpl; import org.caosdb.server.database.backend.interfaces.InsertEntityPropertiesImpl; import org.caosdb.server.database.backend.interfaces.InsertFSODescriptorImpl; import org.caosdb.server.database.backend.interfaces.InsertLinConImpl; -import org.caosdb.server.database.backend.interfaces.InsertLogRecordImpl; import org.caosdb.server.database.backend.interfaces.InsertParentsImpl; import org.caosdb.server.database.backend.interfaces.InsertRoleImpl; import org.caosdb.server.database.backend.interfaces.InsertSparseEntityImpl; @@ -103,11 +98,10 @@ import org.caosdb.server.database.backend.interfaces.ListFilesImpl; import org.caosdb.server.database.backend.interfaces.ListRolesImpl; import org.caosdb.server.database.backend.interfaces.ListUsersImpl; import org.caosdb.server.database.backend.interfaces.LogUserVisitImpl; -import org.caosdb.server.database.backend.interfaces.RegisterSubDomainImpl; import org.caosdb.server.database.backend.interfaces.RetrieveAllImpl; +import org.caosdb.server.database.backend.interfaces.RetrieveCurrentMaxIdImpl; import org.caosdb.server.database.backend.interfaces.RetrieveDatatypesImpl; import org.caosdb.server.database.backend.interfaces.RetrieveEntityACLImpl; -import org.caosdb.server.database.backend.interfaces.RetrieveLogRecordImpl; import org.caosdb.server.database.backend.interfaces.RetrieveParentsImpl; import org.caosdb.server.database.backend.interfaces.RetrievePasswordValidatorImpl; import org.caosdb.server.database.backend.interfaces.RetrievePermissionRulesImpl; @@ -133,6 +127,18 @@ import org.caosdb.server.entity.RetrieveEntity; import org.caosdb.server.utils.UndoHandler; import org.caosdb.server.utils.Undoable; +/** + * Abstract class for backend transactions. + * + * <p>This class is the glue between the Transaction layer and the actual implementation of the + * communication with the back end. + * + * <p>This class also acts as a registry which stores which implementation is being used for which + * backend transaction interface. + * + * @see {@link Transaction} + * @author Timm Fitschen <t.fitschen@indiscale.com> + */ public abstract class BackendTransaction implements Undoable { private final UndoHandler undoHandler = new UndoHandler(); @@ -153,7 +159,7 @@ public abstract class BackendTransaction implements Undoable { } /** - * Intialiaze the adapters to the database backend. + * Initialize the adapters to the database backend. * * <p>Currently this is hard-coded to the MySQL-Backend but the architecture of this class is * designed to make it easy in the future to choose other implementations (i.e. other back-ends) @@ -163,7 +169,6 @@ public abstract class BackendTransaction implements Undoable { setImpl(GetAllNamesImpl.class, MySQLGetAllNames.class); setImpl(DeleteEntityPropertiesImpl.class, MySQLDeleteEntityProperties.class); setImpl(DeleteSparseEntityImpl.class, MySQLDeleteSparseEntity.class); - setImpl(GetChildrenImpl.class, MySQLGetChildren.class); setImpl(GetDependentEntitiesImpl.class, MySQLGetDependentEntities.class); setImpl(GetIDByNameImpl.class, MySQLGetIDByName.class); setImpl(GetInfoImpl.class, MySQLGetInfo.class); @@ -175,7 +180,6 @@ public abstract class BackendTransaction implements Undoable { setImpl(IsSubTypeImpl.class, MySQLIsSubType.class); setImpl(UpdateSparseEntityImpl.class, MySQLUpdateSparseEntity.class); setImpl(RetrieveAllImpl.class, MySQLRetrieveAll.class); - setImpl(RegisterSubDomainImpl.class, MySQLRegisterSubDomain.class); setImpl(RetrieveDatatypesImpl.class, MySQLRetrieveDatatypes.class); setImpl(RetrieveUserImpl.class, MySQLRetrieveUser.class); setImpl(RetrieveParentsImpl.class, MySQLRetrieveParents.class); @@ -197,8 +201,6 @@ public abstract class BackendTransaction implements Undoable { setImpl(SetPermissionRulesImpl.class, MySQLSetPermissionRules.class); setImpl(RetrievePermissionRulesImpl.class, MySQLRetrievePermissionRules.class); setImpl(UpdateUserRolesImpl.class, MySQLUpdateUserRoles.class); - setImpl(InsertLogRecordImpl.class, MySQLInsertLogRecord.class); - setImpl(RetrieveLogRecordImpl.class, MySQLRetrieveLogRecord.class); setImpl(SetQueryTemplateDefinitionImpl.class, MySQLSetQueryTemplateDefinition.class); setImpl( RetrieveQueryTemplateDefinitionImpl.class, MySQLRetrieveQueryTemplateDefinition.class); @@ -212,6 +214,7 @@ public abstract class BackendTransaction implements Undoable { setImpl(LogUserVisitImpl.class, MySQLLogUserVisit.class); setImpl(RetrieveEntityACLImpl.class, MySQLRetrieveEntityACL.class); setImpl(GetVirtualFSOImpl.class, MySQLGetVirtualFSO.class); + setImpl(RetrieveCurrentMaxIdImpl.class, MySQLRetrieveCurrentMaxId.class); } } diff --git a/src/main/java/org/caosdb/server/database/DatabaseAccessManager.java b/src/main/java/org/caosdb/server/database/DatabaseAccessManager.java index f12c78e31bf28799b8c8ead1c9ff311a65bb01ef..478953cbbcd25693dca3df92a3ede6371b4f2212 100644 --- a/src/main/java/org/caosdb/server/database/DatabaseAccessManager.java +++ b/src/main/java/org/caosdb/server/database/DatabaseAccessManager.java @@ -60,6 +60,7 @@ class ReadAccessSemaphore extends Semaphore implements Releasable { 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. public ReadAccessSemaphore() { diff --git a/src/main/java/org/caosdb/server/database/DatabaseUtils.java b/src/main/java/org/caosdb/server/database/DatabaseUtils.java deleted file mode 100644 index d31f10fc0e29b55b223b454c6848b1ea6c7ba0b7..0000000000000000000000000000000000000000 --- a/src/main/java/org/caosdb/server/database/DatabaseUtils.java +++ /dev/null @@ -1,406 +0,0 @@ -/* - * ** header v3.0 - * This file is a part of the CaosDB Project. - * - * Copyright (C) 2018 Research Group Biomedical Physics, - * Max-Planck-Institute for Dynamics and Self-Organization Göttingen - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <https://www.gnu.org/licenses/>. - * - * ** end header - */ -package org.caosdb.server.database; - -import com.google.common.base.Objects; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import org.caosdb.server.database.proto.FlatProperty; -import org.caosdb.server.database.proto.ProtoProperty; -import org.caosdb.server.database.proto.SparseEntity; -import org.caosdb.server.database.proto.VerySparseEntity; -import org.caosdb.server.datatype.AbstractCollectionDatatype; -import org.caosdb.server.datatype.CollectionValue; -import org.caosdb.server.datatype.IndexedSingleValue; -import org.caosdb.server.datatype.ReferenceValue; -import org.caosdb.server.datatype.SingleValue; -import org.caosdb.server.entity.EntityID; -import org.caosdb.server.entity.EntityInterface; -import org.caosdb.server.entity.Message; -import org.caosdb.server.entity.RetrieveEntity; -import org.caosdb.server.entity.Role; -import org.caosdb.server.entity.StatementStatus; -import org.caosdb.server.entity.wrapper.Domain; -import org.caosdb.server.entity.wrapper.Parent; -import org.caosdb.server.entity.wrapper.Property; -import org.caosdb.server.filesystem.FileSystem.ObjectType; -import org.caosdb.server.filesystem.Hash; -import org.caosdb.server.filesystem.Path; - -public class DatabaseUtils { - - public static final String bytes2UTF8(final byte[] bytes) { - if (bytes == null) { - return null; - } else { - return new String(bytes); - } - } - - private static Domain makeReplacement(final EntityInterface p) { - // add a new helper domain. - final Domain d = - new Domain(p.getProperties(), p.getDatatype(), p.getValue(), p.getStatementStatus()); - d.setDescOverride(p.isDescOverride()); - d.setNameOverride(p.isNameOverride()); - d.setDatatypeOverride(p.isDatatypeOverride()); - d.setName(p.getName()); - d.setDescription(p.getDescription()); - p.setReplacement(d); - return d; - } - - public static void deriveStage1Inserts( - final List<EntityInterface> stage1Inserts, final EntityInterface e) { - // entity value - if (e.hasValue()) { - final Property p = new Property(e); - p.setStatementStatus(StatementStatus.FIX); - p.setPIdx(0); - processPropertiesStage1(stage1Inserts, p, e); - } - - for (final EntityInterface p : e.getProperties()) { - processPropertiesStage1(stage1Inserts, p, e); - } - } - - private static void processPropertiesStage1( - final List<EntityInterface> stage1Inserts, final EntityInterface p, final EntityInterface e) { - stage1Inserts.add(p); - if (!p.isDescOverride() - && !p.isNameOverride() - && !p.isDatatypeOverride() - && (!p.hasProperties() || hasUniquePropertyId(p, e)) - && !(p.getDatatype() instanceof AbstractCollectionDatatype)) { - // if p has no sub-properties, just add it - } else { - stage1Inserts.add(makeReplacement(p)); - } - processSubPropertiesStage1(stage1Inserts, p); - } - - public static int deriveStage2Inserts( - final List<EntityInterface> stage2Inserts, final List<EntityInterface> stage1Inserts) { - int domainCount = 0; - for (final EntityInterface p : stage1Inserts) { - if (p.hasRole() && p.getRole() == Role.Domain) { - domainCount++; - } - if (!p.hasReplacement() && p.hasProperties()) { - stage2Inserts.addAll(p.getProperties()); - } - } - return domainCount; - } - - private static boolean hasUniquePropertyId(final EntityInterface p, final EntityInterface e) { - final EntityID id = p.getId(); - for (final EntityInterface p2 : e.getProperties()) { - if (Objects.equal(p2.getId(), id) && p2 != p) { - return false; - } - } - return true; - } - - private static void processSubPropertiesStage1( - final List<EntityInterface> stage1Inserts, final EntityInterface p) { - for (final EntityInterface subP : p.getProperties()) { - if (subP.hasProperties()) { - stage1Inserts.add(makeReplacement(subP)); - processSubPropertiesStage1(stage1Inserts, subP); - } - } - } - - public static void parseFromSparseEntities(final EntityInterface e, final SparseEntity spe) { - if (spe != null) { - e.parseSparseEntity(spe); - } - } - - public static void parseOverrides(final List<FlatProperty> properties, final ResultSet rs) - throws SQLException { - while (rs.next()) { - final int property_id = rs.getInt("property_id"); - for (final FlatProperty p : properties) { - if (p.id == property_id) { - final String name = bytes2UTF8(rs.getBytes("name_override")); - if (name != null) { - p.name = name; - } - final String desc = bytes2UTF8(rs.getBytes("desc_override")); - if (desc != null) { - p.desc = desc; - } - final String type = bytes2UTF8(rs.getBytes("type_override")); - if (type != null) { - p.type = type; - } - final String coll = bytes2UTF8(rs.getBytes("collection_override")); - if (coll != null) { - p.collection = coll; - } - } - } - } - } - - public static List<FlatProperty> parsePropertyResultset(final ResultSet rs) throws SQLException { - final ArrayList<FlatProperty> ret = new ArrayList<FlatProperty>(); - while (rs.next()) { - final FlatProperty fp = new FlatProperty(); - fp.id = rs.getInt("PropertyID"); - fp.value = bytes2UTF8(rs.getBytes("PropertyValue")); - fp.status = bytes2UTF8(rs.getBytes("PropertyStatus")); - fp.idx = rs.getInt("PropertyIndex"); - ret.add(fp); - } - return ret; - } - - /** - * Helper function for parsing MySQL results. - * - * <p>This function creates SparseEntities and parses only the name, the role and the acl of an - * entity. - * - * <p>Never returns null. - */ - public static SparseEntity parseNameRoleACL(final ResultSet rs) throws SQLException { - final SparseEntity ret = new SparseEntity(); - ret.role = bytes2UTF8(rs.getBytes("EntityRole")); - ret.name = bytes2UTF8(rs.getBytes("EntityName")); - ret.acl = bytes2UTF8(rs.getBytes("ACL")); - return ret; - } - - /** - * Helper function for parsing MySQL results. - * - * <p>This function creates SparseEntities and parses all fields which belong to a SparseEntity: - * id, name, role, acl, description, datatype, and the file properties. - * - * <p>Never returns null. - */ - public static SparseEntity parseEntityResultSet(final ResultSet rs) throws SQLException { - final SparseEntity ret = parseNameRoleACL(rs); - ret.id = rs.getInt("EntityID"); - ret.description = bytes2UTF8(rs.getBytes("EntityDesc")); - ret.datatype = bytes2UTF8(rs.getBytes("Datatype")); - ret.collection = bytes2UTF8(rs.getBytes("Collection")); - ret.versionId = bytes2UTF8(rs.getBytes("Version")); - - parseFSODescriptorFields(rs, ret); - return ret; - } - - public static void parseFSODescriptorFields(final ResultSet rs, final SparseEntity ret) - throws SQLException { - final String key = bytes2UTF8(rs.getBytes("FileKey")); - if (key != null) { - ret.fileParentId = rs.getInt("FileParentID"); - ret.filePath = new Path(bytes2UTF8(rs.getBytes("FilePath"))); - ret.fileSize = rs.getLong("FileSize"); - if (rs.wasNull()) { - ret.fileSize = null; - } - ret.fileHash = - Hash.create( - bytes2UTF8(rs.getBytes("FileHash")), - rs.getLong("FileHashChecked"), - bytes2UTF8(rs.getBytes("FileHashAlgo"))); - final String mimetype = bytes2UTF8(rs.getBytes("FileMimeType")); - ret.fileType = - java.util.Objects.equals("inode/directory", mimetype) - ? ObjectType.DIRECTORY - : (Objects.equal("inode/link", mimetype) ? ObjectType.LINK : ObjectType.FILE); - if (ret.fileType == ObjectType.FILE) { - ret.fileMimeType = mimetype; - } - ret.fileStorageId = bytes2UTF8(rs.getBytes("FileStorageID")); - ret.fileKey = key; - - if (ret.fileType == ObjectType.LINK) { - ret.linkTarget = rs.getInt("LinkTarget"); - } - } - } - - public static ArrayList<VerySparseEntity> parseParentResultSet(final ResultSet rs) - throws SQLException { - final ArrayList<VerySparseEntity> ret = new ArrayList<VerySparseEntity>(); - while (rs.next()) { - final VerySparseEntity vsp = new VerySparseEntity(); - vsp.id = rs.getInt("ParentID"); - vsp.name = bytes2UTF8(rs.getBytes("ParentName")); - vsp.description = bytes2UTF8(rs.getBytes("ParentDescription")); - vsp.role = bytes2UTF8(rs.getBytes("ParentRole")); - vsp.acl = bytes2UTF8(rs.getBytes("ACL")); - ret.add(vsp); - } - return ret; - } - - public static <K extends EntityInterface> K parseEntityFromVerySparseEntity( - final K entity, final VerySparseEntity vse) { - entity.setId(new EntityID(vse.id)); - entity.setName(vse.name); - entity.setRole(vse.role); - entity.setDescription(vse.description); - return entity; - } - - public static void parseParentsFromVerySparseEntity( - final EntityInterface entity, final List<VerySparseEntity> pars) { - for (final VerySparseEntity vsp : pars) { - final Parent p = new Parent(new RetrieveEntity(new EntityID(vsp.id))); - p.setName(vsp.name); - p.setDescription(vsp.description); - entity.addParent(p); - } - } - - private static void replace( - final Property p, final Map<EntityID, Property> domainMap, final boolean isHead) { - // ... find the corresponding domain and replace it - ReferenceValue ref; - try { - ref = ReferenceValue.parseReference(p.getValue()); - } catch (final Message e) { - throw new RuntimeException("This should never happen."); - } - final EntityInterface replacement = domainMap.get(ref.getId()); - if (replacement == null) { - if (isHead) { - throw new NullPointerException("Replacement was null"); - } - // entity has been deleted (we are processing properties of an old entity version) - p.setValue(null); - p.addWarning( - new Message( - "The referenced entity has been deleted in the mean time and is not longer available.")); - return; - } - if (replacement.isDescOverride()) { - p.setDescOverride(true); - p.setDescription(replacement.getDescription()); - } - if (replacement.isNameOverride()) { - p.setNameOverride(true); - p.setName(replacement.getName()); - } - if (replacement.isDatatypeOverride()) { - p.setDatatypeOverride(true); - p.setDatatype(replacement.getDatatype()); - } - p.setProperties(replacement.getProperties()); - p.setStatementStatus(replacement.getStatementStatus()); - p.setValue(replacement.getValue()); - } - - public static void transformToDeepPropertyTree( - final EntityInterface e, final List<Property> protoProperties) { - // here we will store every domain we can find and we will index it by - // its id. - final Map<EntityID, Property> domainMap = new HashMap<>(); - - // loop over all properties and collect the domains - for (final Property p : protoProperties) { - // if this is a domain it represents a deeper branch of the property - // tree. - if (p.getRole() == Role.Domain) { - if (domainMap.containsKey(p.getId())) { - // aggregate the multiple values. - final Property domain = domainMap.get(p.getId()); - if (!(domain.getValue() instanceof CollectionValue)) { - final SingleValue singleValue = (SingleValue) domain.getValue(); - final CollectionValue vals = new CollectionValue(); - final IndexedSingleValue iSingleValue = - new IndexedSingleValue(domain.getPIdx(), singleValue); - vals.add(iSingleValue); - domain.setValue(vals); - } - ((CollectionValue) domain.getValue()).add(p.getPIdx(), p.getValue()); - } else { - domainMap.put(p.getId(), p); - } - } - } - - // loop over all properties - final boolean isHead = - e.getVersion().getSuccessors() == null || e.getVersion().getSuccessors().isEmpty(); - for (final Property p : protoProperties) { - - // if this is a replacement - if (p.getStatementStatus() == StatementStatus.REPLACEMENT) { - replace(p, domainMap, isHead); - } - - for (final Property subP : p.getProperties()) { - if (subP.getStatementStatus() == StatementStatus.REPLACEMENT) { - replace(subP, domainMap, isHead); - } - } - - if (p.getId().equals(e.getId())) { - // this is the value of an abstract property. - e.setValue(p.getValue()); - } else if (p.getRole() != Role.Domain) { - // if this is not a domain it is to be added to the properties - // of e. - e.addProperty(p); - } - } - } - - public static ArrayList<Property> parseFromProtoProperties(final List<ProtoProperty> protos) { - final ArrayList<Property> ret = new ArrayList<Property>(); - for (final ProtoProperty pp : protos) { - final Property property = parseFlatProperty(pp.property); - parseFromFlatProperties(property.getProperties(), pp.subProperties); - ret.add(property); - } - return ret; - } - - private static void parseFromFlatProperties( - final List<Property> properties, final List<FlatProperty> props) { - for (final FlatProperty fp : props) { - final Property property = parseFlatProperty(fp); - properties.add(property); - } - } - - private static Property parseFlatProperty(final FlatProperty fp) { - final Property property = new Property(new RetrieveEntity()); - property.parseFlatProperty(fp); - return property; - } -} diff --git a/src/main/java/org/caosdb/server/database/access/AbstractAccess.java b/src/main/java/org/caosdb/server/database/access/AbstractAccess.java index 1d30bd3fee7187b9c44378e5b94c1c6d97287f00..574dabedd0db28ded611411ed8848f00be56a293 100644 --- a/src/main/java/org/caosdb/server/database/access/AbstractAccess.java +++ b/src/main/java/org/caosdb/server/database/access/AbstractAccess.java @@ -1,9 +1,10 @@ /* - * ** header v3.0 * This file is a part of the CaosDB Project. * * Copyright (C) 2018 Research Group Biomedical Physics, * Max-Planck-Institute for Dynamics and Self-Organization Göttingen + * Copyright (C) 2023 IndiScale GmbH <info@indiscale.com> + * Copyright (C) 2023 Timm Fitschen <t.fitschen@indiscale.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -17,8 +18,6 @@ * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. - * - * ** end header */ package org.caosdb.server.database.access; @@ -26,11 +25,18 @@ import java.util.HashMap; import org.caosdb.server.database.misc.DBHelper; import org.caosdb.server.database.misc.RollBackHandler; import org.caosdb.server.transaction.TransactionInterface; +import org.caosdb.server.utils.UseCacheResourceDelegate; -public abstract class AbstractAccess<T extends TransactionInterface> implements Access { +/** + * Abstract implementation of an Access. + * + * @see {@link Access} + * @author Timm Fitschen <t.fitschen@indiscale.com> + */ +public abstract class AbstractAccess<T extends TransactionInterface> + extends UseCacheResourceDelegate implements Access { private HashMap<String, DBHelper> helpers = new HashMap<String, DBHelper>(); - private Boolean useCache = true; private final T transaction; private boolean released = false; @@ -73,22 +79,6 @@ public abstract class AbstractAccess<T extends TransactionInterface> implements } } - @Override - public void setUseCache(final Boolean useCache) { - this.useCache = useCache; - } - /** - * Whether the transaction allows to use the query cache or other caches. This is controlled by - * the "cache" flag. - * - * @see {@link NoCache} - * @return true if caching is encouraged. - */ - @Override - public boolean useCache() { - return this.useCache; - } - @Override public void commit() throws Exception { synchronized (this.helpers) { diff --git a/src/main/java/org/caosdb/server/database/access/Access.java b/src/main/java/org/caosdb/server/database/access/Access.java index 228e8e004aea7454a287bccd898794c2bb4f1b9c..6b8a521cffd5f0d817f9a9b0a4ecce357fb82d76 100644 --- a/src/main/java/org/caosdb/server/database/access/Access.java +++ b/src/main/java/org/caosdb/server/database/access/Access.java @@ -1,9 +1,10 @@ /* - * ** header v3.0 * This file is a part of the CaosDB Project. * * Copyright (C) 2018 Research Group Biomedical Physics, * Max-Planck-Institute for Dynamics and Self-Organization Göttingen + * Copyright (C) 2023 IndiScale GmbH <info@indiscale.com> + * Copyright (C) 2023 Timm Fitschen <t.fitschen@indiscale.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -17,14 +18,29 @@ * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. - * - * ** end header */ package org.caosdb.server.database.access; import org.caosdb.server.database.misc.DBHelper; +import org.caosdb.server.utils.UseCacheResource; -public interface Access { +/** + * Access Interface. + * + * <p>The access object is part of a Transactions state and it is the glue between the Transaction + * and the actual backend implementation of those transactions. It represents different levels of + * access (RO, RW) and provides all the helper objects shared by all the single calls to the backend + * (package .../database/backend/implementation/...). + * + * <p>It is further used to commit or roll-back the owning transaction. + * + * <p>The DatabaseAccessManager issues the Access to a requesting Transaction. + * + * @see {@link Transaction} + * @see {@link DatabaseAccessManager} + * @author Timm Fitschen <t.fitschen@indiscale.com> + */ +public interface Access extends UseCacheResource { public abstract void setHelper(String name, DBHelper helper); @@ -33,8 +49,4 @@ public interface Access { public abstract void release(); public abstract void commit() throws Exception; - - public abstract void setUseCache(Boolean useCache); - - public boolean useCache(); } diff --git a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/DatabaseConnectionPool.java b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/DatabaseConnectionPool.java index 997de3141db6fd49ac510fa61150a2d16e7d5416..8234d948eb3af1dd490e6ef1062de98504e3575d 100644 --- a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/DatabaseConnectionPool.java +++ b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/DatabaseConnectionPool.java @@ -23,7 +23,6 @@ package org.caosdb.server.database.backend.implementation.MySQL; import com.google.common.base.Objects; -import java.sql.CallableStatement; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; @@ -75,8 +74,12 @@ class DatabaseConnectionPool { private static ConnectionPool instance = null; private static synchronized ConnectionPool createConnectionPool() - throws ClassNotFoundException, InstantiationException, IllegalAccessException, SQLException, - ConnectionException, CaosDBException { + throws ClassNotFoundException, + InstantiationException, + IllegalAccessException, + SQLException, + ConnectionException, + CaosDBException { final String url = "jdbc:mysql://" @@ -244,20 +247,6 @@ class DatabaseConnectionPool { System.exit(1); } - // set auto_increment on table entities to the maximum entity_id of - // table transaction_log; - // Why? MYSQL seems to reset the - // auto_increment value each time the MySQL server is being - // restarted to the maximum of ids in the entities table. But if - // some of the ids had been used yet, we don't want to use them - // again. - final CallableStatement prepareCall = con.prepareCall("call initAutoIncrement()"); - try { - prepareCall.execute(); - } catch (final SQLException e) { - logger.error("Could inititialize the autoincrement value for the entities table.", e); - System.exit(1); - } } finally { con.close(); } diff --git a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/DatabaseUtils.java b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/DatabaseUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..e92769fbed4a90182e1622e9c287d8bf88c7d0cd --- /dev/null +++ b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/DatabaseUtils.java @@ -0,0 +1,419 @@ +/* + * This file is a part of the CaosDB Project. + * + * Copyright (C) 2018 Research Group Biomedical Physics, + * Max-Planck-Institute for Dynamics and Self-Organization Göttingen + * Copyright (C) 2023 IndiScale GmbH <info@indiscale.com> + * Copyright (C) 2023 Timm Fitschen <t.fitschen@indiscale.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ +package org.caosdb.server.database.backend.implementation.MySQL; + +import com.google.common.base.Objects; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.AbstractMap.SimpleEntry; +import java.util.ArrayList; +import java.util.Deque; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.stream.Collectors; +import org.caosdb.server.database.proto.FlatProperty; +import org.caosdb.server.database.proto.ProtoProperty; +import org.caosdb.server.database.proto.SparseEntity; +import org.caosdb.server.database.proto.VerySparseEntity; +import org.caosdb.server.datatype.AbstractCollectionDatatype; +import org.caosdb.server.datatype.CollectionValue; +import org.caosdb.server.datatype.GenericValue; +import org.caosdb.server.entity.EntityID; +import org.caosdb.server.entity.EntityInterface; +import org.caosdb.server.entity.Message; +import org.caosdb.server.entity.RetrieveEntity; +import org.caosdb.server.entity.StatementStatus; +import org.caosdb.server.entity.wrapper.Property; +import org.caosdb.server.filesystem.FileSystem.ObjectType; +import org.caosdb.server.filesystem.Hash; +import org.caosdb.server.filesystem.Path; + +public class DatabaseUtils { + + private static final String DELETED_REFERENCE_IN_PREVIOUS_VERSION = + "DELETED_REFERENCE_IN_PREVIOUS_VERSION"; + + public static final String bytes2UTF8(final byte[] bytes) { + if (bytes == null) { + return null; + } else { + return new String(bytes); + } + } + + public static int deriveStage1Inserts( + final List<Property> stage1Inserts, final EntityInterface e) { + // entity value + if (e.hasValue()) { + final Property p = new Property(e); + p.setStatementStatus(StatementStatus.FIX); + p.setDatatype(e.getDatatype()); + p.setValue(e.getValue()); + p.setPIdx(0); + processPropertiesStage1(stage1Inserts, p, e); + } + + for (final Property p : e.getProperties()) { + processPropertiesStage1(stage1Inserts, p, e); + } + int replacementCount = 0; + for (EntityInterface pp : stage1Inserts) { + if (pp instanceof Replacement) { + replacementCount++; + } + } + return replacementCount; + } + + private static void processPropertiesStage1( + final List<Property> stage1Inserts, final Property p, final EntityInterface e) { + if (!p.isDescOverride() + && !p.isNameOverride() + && !p.isDatatypeOverride() + && (!p.hasProperties() || hasUniquePropertyId(p, e)) + && !(p.getDatatype() instanceof AbstractCollectionDatatype)) { + stage1Inserts.add(p); + processSubPropertiesStage1(stage1Inserts, p); + } else { + Replacement r = new Replacement(p); + stage1Inserts.add(r); + stage1Inserts.add(r.replacement); + processSubPropertiesStage1(stage1Inserts, r); + } + } + + public static void deriveStage2Inserts( + final List<Property> stage2Inserts, + final List<Property> stage1Inserts, + Deque<EntityID> replacementIds, + EntityInterface entity) { + for (final Property p : stage1Inserts) { + if (p instanceof Replacement) { + if (!p.hasId() || p.getId().isTemporary()) { + EntityID replacementId = replacementIds.pop(); + ((Replacement) p).setReplacementId(replacementId); + } + } + if (p.hasProperties()) { + if (p instanceof Replacement && ((Replacement) p).isStage2Replacement()) { + stage2Inserts.add(((Replacement) p).replacement); + } + for (Property subP : p.getProperties()) { + if (!subP.hasProperties()) { + stage2Inserts.add(subP); + } + } + } + } + } + + private static boolean hasUniquePropertyId(final EntityInterface p, final EntityInterface e) { + final EntityID id = p.getId(); + for (final EntityInterface p2 : e.getProperties()) { + if (Objects.equal(p2.getId(), id) && p2 != p) { + return false; + } + } + return true; + } + + private static void processSubPropertiesStage1( + final List<Property> stage1Inserts, final EntityInterface p) { + for (final Property subP : p.getProperties()) { + subP.setDomain(p); + if (subP.hasProperties()) { + Replacement r = new Replacement(subP); + r.setStage2Replacement(true); + stage1Inserts.add(r); + processSubPropertiesStage1(stage1Inserts, r); + } + } + } + + public static void parseOverrides(final List<ProtoProperty> properties, final ResultSet rs) + throws SQLException { + while (rs.next()) { + String property_id = rs.getString("property_id"); + if (rs.wasNull()) property_id = rs.getString("InternalPropertyID"); + for (final FlatProperty p : properties) { + if (p.id.equals(property_id)) { + final String name = bytes2UTF8(rs.getBytes("name_override")); + if (name != null) { + p.name = name; + } + final String desc = bytes2UTF8(rs.getBytes("desc_override")); + if (desc != null) { + p.desc = desc; + } + final String type_id = bytes2UTF8(rs.getBytes("type_id_override")); + if (type_id != null) { + p.type_id = type_id; + p.type_name = bytes2UTF8(rs.getBytes("type_name_override")); + } + final String coll = bytes2UTF8(rs.getBytes("collection_override")); + if (coll != null) { + p.collection = coll; + } + } + } + } + } + + public static List<ProtoProperty> parsePropertyResultset(final ResultSet rs) throws SQLException { + final List<ProtoProperty> ret = new LinkedList<>(); + while (rs.next()) { + ProtoProperty pp = new ProtoProperty(); + pp.id = rs.getString("PropertyID"); + if (rs.wasNull()) { + pp.id = rs.getString("InternalPropertyID"); + } + pp.value = bytes2UTF8(rs.getBytes("PropertyValue")); + pp.status = bytes2UTF8(rs.getBytes("PropertyStatus")); + pp.idx = rs.getInt("PropertyIndex"); + ret.add(pp); + } + return ret; + } + + /** + * Helper function for parsing MySQL results. + * + * <p>This function creates SparseEntities and parses only the name, the role and the acl of an + * entity. + * + * <p>Never returns null. + */ + public static SparseEntity parseNameRoleACL(final ResultSet rs) throws SQLException { + final SparseEntity ret = new SparseEntity(); + ret.role = bytes2UTF8(rs.getBytes("EntityRole")); + ret.name = bytes2UTF8(rs.getBytes("EntityName")); + ret.acl = bytes2UTF8(rs.getBytes("ACL")); + return ret; + } + + /** + * Helper function for parsing MySQL results. + * + * <p>This function creates SparseEntities and parses all fields which belong to a SparseEntity: + * id, name, role, acl, description, datatype, and the file properties. + * + * <p>Never returns null. + */ + public static SparseEntity parseEntityResultSet(final ResultSet rs) throws SQLException { + final SparseEntity ret = parseNameRoleACL(rs); + ret.id = rs.getString("EntityID"); + ret.description = bytes2UTF8(rs.getBytes("EntityDesc")); + ret.datatype_id = bytes2UTF8(rs.getBytes("DatatypeID")); + ret.datatype_name = bytes2UTF8(rs.getBytes("DatatypeName")); + ret.collection = bytes2UTF8(rs.getBytes("Collection")); + + ret.versionId = bytes2UTF8(rs.getBytes("Version")); + parseFSODescriptorFields(rs, ret); + return ret; + } + + public static void parseFSODescriptorFields(final ResultSet rs, final SparseEntity ret) + throws SQLException { + final String key = bytes2UTF8(rs.getBytes("FileKey")); + if (key != null) { + ret.fileParentId = rs.getString("FileParentID"); + ret.filePath = new Path(bytes2UTF8(rs.getBytes("FilePath"))); + ret.fileSize = rs.getLong("FileSize"); + if (rs.wasNull()) { + ret.fileSize = null; + } + ret.fileHash = + Hash.create( + bytes2UTF8(rs.getBytes("FileHash")), + rs.getLong("FileHashChecked"), + bytes2UTF8(rs.getBytes("FileHashAlgo"))); + final String mimetype = bytes2UTF8(rs.getBytes("FileMimeType")); + ret.fileType = + java.util.Objects.equals("inode/directory", mimetype) + ? ObjectType.DIRECTORY + : (Objects.equal("inode/link", mimetype) ? ObjectType.LINK : ObjectType.FILE); + if (ret.fileType == ObjectType.FILE) { + ret.fileMimeType = mimetype; + } + ret.fileStorageId = bytes2UTF8(rs.getBytes("FileStorageID")); + ret.fileKey = key; + + if (ret.fileType == ObjectType.LINK) { + ret.linkTarget = rs.getString("LinkTarget"); + } + } + } + + public static LinkedList<VerySparseEntity> parseParentResultSet(final ResultSet rs) + throws SQLException { + final LinkedList<VerySparseEntity> ret = new LinkedList<>(); + while (rs.next()) { + final VerySparseEntity vsp = new VerySparseEntity(); + vsp.id = rs.getString("ParentID"); + vsp.name = bytes2UTF8(rs.getBytes("ParentName")); + vsp.description = bytes2UTF8(rs.getBytes("ParentDescription")); + vsp.role = bytes2UTF8(rs.getBytes("ParentRole")); + vsp.acl = bytes2UTF8(rs.getBytes("ACL")); + ret.add(vsp); + } + return ret; + } + + public static ArrayList<Property> parseFromProtoProperties( + EntityInterface entity, List<ProtoProperty> protos) { + final ArrayList<Property> ret = new ArrayList<Property>(); + for (final ProtoProperty pp : protos) { + if (pp.id.equals(entity.getId().toString())) { + if (pp.value != null) { + entity.setValue(new GenericValue(pp.value)); + } + if (pp.collValues != null) { + CollectionValue value = new CollectionValue(); + for (Object next : pp.collValues) { + if (next == null) { + value.add(null); + } else { + value.add(new GenericValue(next.toString())); + } + } + entity.setValue(value); + } + + } else { + Message warning = null; + if (pp.status == DELETED_REFERENCE_IN_PREVIOUS_VERSION) { + pp.status = StatementStatus.FIX.name(); + warning = + new Message( + "The referenced entity has been deleted in the mean time and is not longer available."); + } + final Property property = parseFlatProperty(pp); + if (warning != null) { + property.addWarning(warning); + } + ret.add(property); + } + } + return ret; + } + + private static Property parseFlatProperty(final ProtoProperty fp) { + final Property property = new Property(new RetrieveEntity()); + property.parseProtoProperty(fp); + return property; + } + + @SuppressWarnings("unchecked") + public static LinkedList<ProtoProperty> transformToDeepPropertyTree( + List<ProtoProperty> properties, boolean isHead) { + LinkedList<ProtoProperty> result = new LinkedList<>(); + Map<String, ProtoProperty> replacements = new HashMap<>(); + for (ProtoProperty pp : properties) { + if (pp.status.equals(ReplacementStatus.REPLACEMENT.name())) { + replacements.put(pp.value, null); + } + if (pp.subProperties != null) { + for (ProtoProperty subP : pp.subProperties) { + if (subP.status.equals(ReplacementStatus.REPLACEMENT.name())) { + + replacements.put(subP.value, null); + } + } + } + } + Iterator<ProtoProperty> iterator = properties.iterator(); + while (iterator.hasNext()) { + ProtoProperty pp = iterator.next(); + if (replacements.containsKey(pp.id.toString())) { + ProtoProperty other = replacements.get(pp.id.toString()); + // handle collection values + if (other != null) { + if (other.collValues == null) { + other.collValues = new LinkedList<>(); + Entry<Integer, String> obj = new SimpleEntry<>(other.idx, other.value); + other.collValues.add(obj); + other.value = null; + } + other.collValues.add(new SimpleEntry<>(pp.idx, pp.value)); + } else { + replacements.put(pp.id.toString(), pp); + } + iterator.remove(); + } + } + for (ProtoProperty pp : properties) { + if (pp.status.equals(ReplacementStatus.REPLACEMENT.name())) { + replace(pp, replacements.get(pp.value), isHead); + } + if (pp.subProperties != null) { + for (ProtoProperty subP : pp.subProperties) { + if (subP.status.equals(ReplacementStatus.REPLACEMENT.name())) { + replace(subP, replacements.get(subP.value), isHead); + } + } + } + if (pp.collValues != null) { + // sort + pp.collValues.sort( + (x, y) -> { + return ((Entry<Integer, String>) x).getKey() - ((Entry<Integer, String>) y).getKey(); + }); + pp.collValues = + pp.collValues.stream() + .map((x) -> (Object) ((Entry<Integer, String>) x).getValue()) + .collect(Collectors.toList()); + } + result.add(pp); + } + return result; + } + + /* + * This replace function is used during the retrieval of properties. It is + * basically the opposite of the Replacement class. It copies the information + * from the replacement object back into the Property. + */ + private static void replace(ProtoProperty pp, ProtoProperty replacement, boolean isHead) { + if (replacement == null) { + if (isHead) { + throw new NullPointerException("Replacement was null"); + } + // entity has been deleted (we are processing properties of an old entity version) + pp.value = null; + pp.status = DELETED_REFERENCE_IN_PREVIOUS_VERSION; + return; + } + pp.desc = replacement.desc; + pp.name = replacement.name; + pp.type_id = replacement.type_id; + pp.type_name = replacement.type_name; + pp.collection = replacement.collection; + pp.value = replacement.value; + pp.collValues = replacement.collValues; + pp.status = replacement.status; + pp.subProperties = replacement.subProperties; + } +} diff --git a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLDeleteEntityProperties.java b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLDeleteEntityProperties.java index f8cba7d4c9734c1e6e12b72498ceaa6c57ed1a17..747cf50d8390d943fce5ffbf596debe5659b8df9 100644 --- a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLDeleteEntityProperties.java +++ b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLDeleteEntityProperties.java @@ -1,9 +1,10 @@ /* - * ** header v3.0 * This file is a part of the CaosDB Project. * * Copyright (C) 2018 Research Group Biomedical Physics, * Max-Planck-Institute for Dynamics and Self-Organization Göttingen + * Copyright (C) 2023 IndiScale GmbH <info@indiscale.com> + * Copyright (C) 2023 Timm Fitschen <t.fitschen@indiscale.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -17,8 +18,6 @@ * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. - * - * ** end header */ package org.caosdb.server.database.backend.implementation.MySQL; @@ -44,7 +43,7 @@ public class MySQLDeleteEntityProperties extends MySQLTransaction try { final PreparedStatement stmt = prepareStatement(STMT_DELETE_ENTITY_PROPERTIES); - stmt.setInt(1, id.toInteger()); + stmt.setString(1, id.toString()); stmt.execute(); } catch (final SQLIntegrityConstraintViolationException exc) { throw new IntegrityException(exc); diff --git a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLDeleteSparseEntity.java b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLDeleteSparseEntity.java index 7fe8ac303b53ec26dc947c1e4a84e648d7b6517d..a1ab389b6878c06c5fd965d0952d422f11134c0e 100644 --- a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLDeleteSparseEntity.java +++ b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLDeleteSparseEntity.java @@ -1,9 +1,10 @@ /* - * ** header v3.0 * This file is a part of the CaosDB Project. * * Copyright (C) 2018 Research Group Biomedical Physics, * Max-Planck-Institute for Dynamics and Self-Organization Göttingen + * Copyright (C) 2023 IndiScale GmbH <info@indiscale.com> + * Copyright (C) 2023 Timm Fitschen <t.fitschen@indiscale.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -17,8 +18,6 @@ * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. - * - * ** end header */ package org.caosdb.server.database.backend.implementation.MySQL; @@ -44,7 +43,7 @@ public class MySQLDeleteSparseEntity extends MySQLTransaction implements DeleteS try { final PreparedStatement stmt = prepareStatement(STMT_DELETE_SPARSE_ENTITY); - stmt.setInt(1, id.toInteger()); + stmt.setString(1, id.toString()); stmt.execute(); } catch (final SQLIntegrityConstraintViolationException exc) { throw new IntegrityException(exc); diff --git a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLGetAllNames.java b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLGetAllNames.java index 09cae6886c2daa94d93ab76539a3e1343f5aecbe..474048f1a72331c3a2e35b38c5cb4c9b3e4d6543 100644 --- a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLGetAllNames.java +++ b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLGetAllNames.java @@ -5,7 +5,6 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; -import org.caosdb.server.database.DatabaseUtils; import org.caosdb.server.database.access.Access; import org.caosdb.server.database.backend.interfaces.GetAllNamesImpl; import org.caosdb.server.database.exceptions.TransactionException; diff --git a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLGetChildren.java b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLGetChildren.java deleted file mode 100644 index 3c1cebf1e20a720a5f726e378bb22b1ad61c850a..0000000000000000000000000000000000000000 --- a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLGetChildren.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * ** header v3.0 - * This file is a part of the CaosDB Project. - * - * Copyright (C) 2018 Research Group Biomedical Physics, - * Max-Planck-Institute for Dynamics and Self-Organization Göttingen - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <https://www.gnu.org/licenses/>. - * - * ** end header - */ -package org.caosdb.server.database.backend.implementation.MySQL; - -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.util.LinkedList; -import java.util.List; -import org.caosdb.server.database.access.Access; -import org.caosdb.server.database.backend.interfaces.GetChildrenImpl; -import org.caosdb.server.database.exceptions.TransactionException; -import org.caosdb.server.entity.EntityID; - -public class MySQLGetChildren extends MySQLTransaction implements GetChildrenImpl { - - public MySQLGetChildren(final Access access) { - super(access); - } - - public static final String STMT_GET_CHILDREN = - "Select child from isa_cache where parent=? and rpath=child"; - - @Override - public List<EntityID> execute(final EntityID entity) throws TransactionException { - try { - final PreparedStatement stmt = prepareStatement(STMT_GET_CHILDREN); - - stmt.setInt(1, entity.toInteger()); - - ResultSet rs = null; - try { - rs = stmt.executeQuery(); - final List<EntityID> ret = new LinkedList<>(); - while (rs.next()) { - ret.add(new EntityID(rs.getInt(1))); - } - return ret; - } finally { - if (rs != null && !rs.isClosed()) { - rs.close(); - } - } - } catch (final Exception e) { - throw new TransactionException(e); - } - } -} diff --git a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLGetDependentEntities.java b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLGetDependentEntities.java index 9384c8a04861a01f1e56236b0ab42433d87990f5..2af80f802ca19eca806856bb653788b50ae5ecf9 100644 --- a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLGetDependentEntities.java +++ b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLGetDependentEntities.java @@ -1,9 +1,10 @@ /* - * ** header v3.0 * This file is a part of the CaosDB Project. * * Copyright (C) 2018 Research Group Biomedical Physics, * Max-Planck-Institute for Dynamics and Self-Organization Göttingen + * Copyright (C) 2023 IndiScale GmbH <info@indiscale.com> + * Copyright (C) 2023 Timm Fitschen <t.fitschen@indiscale.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -17,8 +18,6 @@ * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. - * - * ** end header */ package org.caosdb.server.database.backend.implementation.MySQL; @@ -31,6 +30,15 @@ import org.caosdb.server.database.backend.interfaces.GetDependentEntitiesImpl; import org.caosdb.server.database.exceptions.TransactionException; import org.caosdb.server.entity.EntityID; +/** + * Get a list of ids of all entities which require the given entity to exists. + * + * <p>That is the list of all direct child entities (by is-a relation), referenced entities (i.e. + * the given entity is a property's value), all properties which have this entity as their data type + * and all implemented properties which use this entity as their abtract property. + * + * @author Timm Fitschen <t.fitschen@indiscale.com> + */ public class MySQLGetDependentEntities extends MySQLTransaction implements GetDependentEntitiesImpl { @@ -45,13 +53,13 @@ public class MySQLGetDependentEntities extends MySQLTransaction try { final PreparedStatement stmt = prepareStatement(STMT_GET_DEPENDENT_ENTITIES); - stmt.setInt(1, entity.toInteger()); + stmt.setString(1, entity.toString()); final ResultSet rs = stmt.executeQuery(); try { final List<EntityID> ret = new LinkedList<>(); while (rs.next()) { - ret.add(new EntityID(rs.getInt(1))); + ret.add(new EntityID(rs.getString(1))); } return ret; } finally { diff --git a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLGetFileEntityByPath.java b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLGetFileEntityByPath.java index 54234d86ad9ab8029d9ed313caacd333ecd933b3..cbb7ea6030467e1c642a267474acced3153fc29a 100644 --- a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLGetFileEntityByPath.java +++ b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLGetFileEntityByPath.java @@ -25,7 +25,6 @@ package org.caosdb.server.database.backend.implementation.MySQL; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; -import org.caosdb.server.database.DatabaseUtils; import org.caosdb.server.database.access.Access; import org.caosdb.server.database.backend.interfaces.GetFileEntityByPathImpl; import org.caosdb.server.database.exceptions.TransactionException; @@ -74,7 +73,7 @@ public class MySQLGetFileEntityByPath extends MySQLTransaction implements GetFil try (ResultSet rs = stmt.executeQuery()) { if (rs.next()) { final SparseEntity ret = new SparseEntity(); - ret.id = rs.getInt("FileId"); + ret.id = rs.getString("FileId"); DatabaseUtils.parseFSODescriptorFields(rs, ret); return ret; } else { @@ -86,7 +85,7 @@ public class MySQLGetFileEntityByPath extends MySQLTransaction implements GetFil private SparseEntity retrieveEntityACL(final SparseEntity entity) throws SQLException, ConnectionException { final PreparedStatement stmt = prepareStatement(STMT_GET_ACL); - stmt.setInt(1, entity.id); + stmt.setString(1, entity.id); try (ResultSet rs = stmt.executeQuery()) { rs.next(); entity.acl = DatabaseUtils.bytes2UTF8(rs.getBytes("ACL")); diff --git a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLGetFilesInDirectory.java b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLGetFilesInDirectory.java index d7803633f4de41a29f6a12ebc72de8ddbec51d25..7bb4543fa28ec4726369cd6bd3c3af4f822b553c 100644 --- a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLGetFilesInDirectory.java +++ b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLGetFilesInDirectory.java @@ -5,7 +5,6 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.util.LinkedList; import java.util.List; -import org.caosdb.server.database.DatabaseUtils; import org.caosdb.server.database.access.Access; import org.caosdb.server.database.exceptions.TransactionException; import org.caosdb.server.database.proto.SparseEntity; @@ -24,12 +23,12 @@ public class MySQLGetFilesInDirectory extends MySQLTransaction public List<SparseEntity> execute(final EntityID directory_id) { try { final PreparedStatement stmt = prepareStatement(STMT_GET_FILES_IN_DIR); - stmt.setInt(1, directory_id.toInteger()); + stmt.setString(1, directory_id.toString()); final List<SparseEntity> result = new LinkedList<>(); try (ResultSet rs = stmt.executeQuery()) { while (rs.next()) { final SparseEntity entity = new SparseEntity(); - entity.id = rs.getInt("FileId"); + entity.id = rs.getString("FileId"); DatabaseUtils.parseFSODescriptorFields(rs, entity); result.add(entity); } diff --git a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLGetIDByName.java b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLGetIDByName.java index c2e28cd39929f71e874fb4a69c38261ed290b18a..5d3e3a7d707c0d7d451d0229426a959fc827e8ef 100644 --- a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLGetIDByName.java +++ b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLGetIDByName.java @@ -1,9 +1,10 @@ /* - * ** header v3.0 * This file is a part of the CaosDB Project. * * Copyright (C) 2018 Research Group Biomedical Physics, * Max-Planck-Institute for Dynamics and Self-Organization Göttingen + * Copyright (C) 2023 IndiScale GmbH <info@indiscale.com> + * Copyright (C) 2023 Timm Fitschen <t.fitschen@indiscale.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -17,13 +18,12 @@ * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. - * - * ** end header */ package org.caosdb.server.database.backend.implementation.MySQL; import java.sql.PreparedStatement; import java.sql.ResultSet; +import java.sql.Types; import java.util.LinkedList; import java.util.List; import org.caosdb.server.database.access.Access; @@ -31,6 +31,11 @@ import org.caosdb.server.database.backend.interfaces.GetIDByNameImpl; import org.caosdb.server.database.exceptions.TransactionException; import org.caosdb.server.entity.EntityID; +/** + * Retrieve the entity id of an entity with the given name (or null). + * + * @author Timm Fitschen <t.fitschen@indiscale.com> + */ public class MySQLGetIDByName extends MySQLTransaction implements GetIDByNameImpl { public MySQLGetIDByName(final Access access) { @@ -42,34 +47,29 @@ public class MySQLGetIDByName extends MySQLTransaction implements GetIDByNameImp * Therefore {@link #STMT_AND_ROLE}, {@link #STMT_NOT_ROLE}, and {@link #STMT_LIMIT} can as * additional conditions. */ - public static final String STMT_GET_ID_BY_NAME = - "Select n.entity_id AS id " - + "FROM name_data AS n JOIN entities AS e " - + "ON (n.domain_id=0 AND n.property_id=20 AND e.id = n.entity_id)" - + "WHERE n.value=?"; - - public static final String STMT_AND_ROLE = " AND e.role=?"; - public static final String STMT_NOT_ROLE = " AND e.role!='ROLE'"; - public static final String STMT_LIMIT = " LIMIT "; + public static final String STMT_GET_ID_BY_NAME = "call getIdByName(?, ?, ?)"; @Override public List<EntityID> execute(final String name, final String role, final String limit) throws TransactionException { try { - final String stmtStr = - STMT_GET_ID_BY_NAME - + (role != null ? STMT_AND_ROLE : STMT_NOT_ROLE) - + (limit != null ? STMT_LIMIT + limit : ""); - final PreparedStatement stmt = prepareStatement(stmtStr); + final PreparedStatement stmt = prepareStatement(STMT_GET_ID_BY_NAME); stmt.setString(1, name); if (role != null) { stmt.setString(2, role); + } else { + stmt.setNull(2, Types.VARCHAR); + } + if (limit != null) { + stmt.setString(3, limit); + } else { + stmt.setNull(3, Types.VARCHAR); } try (ResultSet rs = stmt.executeQuery()) { final List<EntityID> ret = new LinkedList<>(); while (rs.next()) { - ret.add(new EntityID(rs.getInt("id"))); + ret.add(new EntityID(rs.getString("id"))); } return ret; diff --git a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLGetUpdateableChecksums.java b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLGetUpdateableChecksums.java index fde9134665b20e82c756d66be54070d46fc39d9d..f619a3c5f3eefafde753db6c51aba81b14f18a38 100644 --- a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLGetUpdateableChecksums.java +++ b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLGetUpdateableChecksums.java @@ -1,9 +1,10 @@ /* - * ** header v3.0 * This file is a part of the CaosDB Project. * * Copyright (C) 2018 Research Group Biomedical Physics, * Max-Planck-Institute for Dynamics and Self-Organization Göttingen + * Copyright (C) 2023 IndiScale GmbH <info@indiscale.com> + * Copyright (C) 2023 Timm Fitschen <t.fitschen@indiscale.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -17,8 +18,6 @@ * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. - * - * ** end header */ package org.caosdb.server.database.backend.implementation.MySQL; @@ -30,11 +29,17 @@ import org.caosdb.server.database.backend.interfaces.GetUpdateableChecksumsImpl; import org.caosdb.server.database.exceptions.TransactionException; import org.caosdb.server.entity.EntityID; +/** + * Retrieve the entity ids of all files which have a checksum which should be updated (because the + * checksum is NULL, currently). + * + * @author Timm Fitschen <t.fitschen@indiscale.com> + */ public class MySQLGetUpdateableChecksums extends MySQLTransaction implements GetUpdateableChecksumsImpl { private final String GET_UPDATEABLE_CHECKSUMS = - "SELECT file_id FROM files WHERE hash IS NULL AND ( mimetype != 'inode/directory' AND mimetype != 'inode/link' OR mimetype IS NULL ) LIMIT 1"; + "SELECT (SELECT id FROM entity_ids WHERE internal_id = files.file_id) AS entity_id FROM files WHERE hash IS NULL AND ( mimetype != 'inode/directory' AND mimetype != 'inode/link' OR mimetype IS NULL ) LIMIT 1"; public MySQLGetUpdateableChecksums(final Access access) { super(access); @@ -46,7 +51,7 @@ public class MySQLGetUpdateableChecksums extends MySQLTransaction final PreparedStatement stmt = prepareStatement(this.GET_UPDATEABLE_CHECKSUMS); final ResultSet rs = stmt.executeQuery(); if (rs.next()) { - return new EntityID(rs.getInt(1)); + return new EntityID(rs.getString(1)); } else { return null; } diff --git a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLGetVirtualFSO.java b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLGetVirtualFSO.java index 21f8f0990a34c084e6d30f59e709cf2dfdece90d..f1efabfbea150a31485bf2e7eb0fdd3af7c1859b 100644 --- a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLGetVirtualFSO.java +++ b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLGetVirtualFSO.java @@ -3,7 +3,6 @@ package org.caosdb.server.database.backend.implementation.MySQL; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; -import org.caosdb.server.database.DatabaseUtils; import org.caosdb.server.database.access.Access; import org.caosdb.server.database.backend.interfaces.GetVirtualFSOImpl; import org.caosdb.server.database.exceptions.TransactionException; @@ -28,7 +27,7 @@ public class MySQLGetVirtualFSO extends MySQLTransaction implements GetVirtualFS ResultSet resultSet = stmt.executeQuery(); if (resultSet.next()) { SparseEntity e = new SparseEntity(); - e.id = resultSet.getInt("FileId"); + e.id = resultSet.getString("FileId"); DatabaseUtils.parseFSODescriptorFields(resultSet, e); return new FSODescriptor(e); } diff --git a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLInsertEntityDatatype.java b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLInsertEntityDatatype.java index c4cb7cf98e5da51f98d71b4c5f6fc91e6961892e..40c7a40754d823ce20580b671147c9cf21576af7 100644 --- a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLInsertEntityDatatype.java +++ b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLInsertEntityDatatype.java @@ -1,3 +1,22 @@ +/* + * This file is a part of the CaosDB Project. + * + * Copyright (C) 2023 IndiScale GmbH <info@indiscale.com> + * Copyright (C) 2023 Timm Fitschen <t.fitschen@indiscale.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ package org.caosdb.server.database.backend.implementation.MySQL; import java.sql.PreparedStatement; @@ -8,6 +27,15 @@ import org.caosdb.server.database.exceptions.IntegrityException; import org.caosdb.server.database.exceptions.TransactionException; import org.caosdb.server.database.proto.SparseEntity; +/** + * Insert an entity's (meaning: an abstract property's) data type. + * + * <p>This inserts the abstract property's data type as opposed to the (overridden data type which + * is being inserted/updated via InsertEntityPropertiesImpl. + * + * @see {@link InsertEntityPropertiesImpl} + * @author Timm Fitschen <t.fitschen@indiscale.com> + */ public class MySQLInsertEntityDatatype extends MySQLTransaction implements InsertEntityDatatypeImpl { @@ -19,18 +47,13 @@ public class MySQLInsertEntityDatatype extends MySQLTransaction * Inserts atomic data types of properties into the data_type table. Has two parameters, the * property_id and the data type name. */ - public static final String STMT_INSERT_ENTITY_DATATYPE = - "INSERT INTO data_type (domain_id, entity_id, property_id, datatype) " - + "SELECT 0, 0, ?, " - + "( SELECT entity_id FROM name_data WHERE domain_id = 0 AND property_id = 20 AND value = ? LIMIT 1);"; + public static final String STMT_INSERT_ENTITY_DATATYPE = "call insertEntityDataType(?, ?)"; /** * Inserts collection data types of properties into the data_type table. Has two parameters, the * property_id and the type of collection (e.g. 'LIST'). */ - public static final String STMT_INSERT_ENTITY_COLLECTION = - "INSERT INTO collection_type (domain_id, entity_id, property_id, collection) " - + "SELECT 0, 0, ?, ?;"; + public static final String STMT_INSERT_ENTITY_COLLECTION = "call insertEntityCollection(?, ?)"; @Override public void execute(final SparseEntity entity) { @@ -38,8 +61,8 @@ public class MySQLInsertEntityDatatype extends MySQLTransaction final PreparedStatement insertEntityDatatypeStmt = prepareStatement(STMT_INSERT_ENTITY_DATATYPE); - insertEntityDatatypeStmt.setInt(1, entity.id); - insertEntityDatatypeStmt.setString(2, entity.datatype); + insertEntityDatatypeStmt.setString(1, entity.id); + insertEntityDatatypeStmt.setString(2, entity.datatype_id); insertEntityDatatypeStmt.execute(); @@ -47,7 +70,7 @@ public class MySQLInsertEntityDatatype extends MySQLTransaction final PreparedStatement insertEntityCollectionStmt = prepareStatement(STMT_INSERT_ENTITY_COLLECTION); - insertEntityCollectionStmt.setInt(1, entity.id); + insertEntityCollectionStmt.setString(1, entity.id); insertEntityCollectionStmt.setString(2, entity.collection); insertEntityCollectionStmt.execute(); diff --git a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLInsertEntityProperties.java b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLInsertEntityProperties.java index 7c3c5a33e160f3da6a8c2b89fe0487e0317c99da..d303acca337cf196ddd251666d71ccb2d21ee21c 100644 --- a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLInsertEntityProperties.java +++ b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLInsertEntityProperties.java @@ -1,9 +1,10 @@ /* - * ** header v3.0 * This file is a part of the CaosDB Project. * * Copyright (C) 2018 Research Group Biomedical Physics, * Max-Planck-Institute for Dynamics and Self-Organization Göttingen + * Copyright (C) 2023 IndiScale GmbH <info@indiscale.com> + * Copyright (C) 2023 Timm Fitschen <t.fitschen@indiscale.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -17,24 +18,46 @@ * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. - * - * ** end header */ package org.caosdb.server.database.backend.implementation.MySQL; import static java.sql.Types.BIGINT; import java.sql.PreparedStatement; +import java.sql.ResultSet; import java.sql.SQLException; import java.sql.SQLIntegrityConstraintViolationException; +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; import org.caosdb.server.database.access.Access; import org.caosdb.server.database.backend.interfaces.InsertEntityPropertiesImpl; import org.caosdb.server.database.exceptions.IntegrityException; import org.caosdb.server.database.exceptions.TransactionException; import org.caosdb.server.database.proto.FlatProperty; +import org.caosdb.server.datatype.AbstractCollectionDatatype; import org.caosdb.server.datatype.AbstractDatatype.Table; +import org.caosdb.server.datatype.CollectionValue; +import org.caosdb.server.datatype.IndexedSingleValue; +import org.caosdb.server.datatype.ReferenceValue; +import org.caosdb.server.datatype.SingleValue; import org.caosdb.server.entity.EntityID; +import org.caosdb.server.entity.EntityInterface; +import org.caosdb.server.entity.wrapper.Property; +/** + * Insert the entity's properties. + * + * <p>This implementation transforms a deep tree of properties into a flat representation. This + * transformation is a MySQL-backend implementation detail and should not be leaked to the clients. + * + * <p>The reverse transformation happens in MySQLRetrieveProperties. + * + * @see {@link MySQLRetrieveProperties} + * @author Timm Fitschen <t.fitschen@indiscale.com> + */ public class MySQLInsertEntityProperties extends MySQLTransaction implements InsertEntityPropertiesImpl { @@ -46,7 +69,27 @@ public class MySQLInsertEntityProperties extends MySQLTransaction } @Override - public void execute( + public void execute(EntityInterface entity) { + final List<Property> stage1Inserts = new LinkedList<>(); + final List<Property> stage2Inserts = new LinkedList<>(); + + final int domainCount = DatabaseUtils.deriveStage1Inserts(stage1Inserts, entity); + + try { + final Deque<EntityID> replacementIds = registerReplacementId(domainCount); + + DatabaseUtils.deriveStage2Inserts(stage2Inserts, stage1Inserts, replacementIds, entity); + + insertStages(stage1Inserts, entity.getDomain(), entity.getId()); + insertStages(stage2Inserts, entity.getId(), null); + } catch (final SQLIntegrityConstraintViolationException exc) { + throw new IntegrityException(exc); + } catch (SQLException | ConnectionException e) { + throw new TransactionException(e); + } + } + + private void insertFlatProperty( final EntityID domain, final EntityID entity, final FlatProperty fp, @@ -56,9 +99,9 @@ public class MySQLInsertEntityProperties extends MySQLTransaction try { final PreparedStatement stmt = prepareStatement(STMT_INSERT_ENTITY_PROPERTY); - stmt.setInt(1, domain.toInteger()); - stmt.setInt(2, entity.toInteger()); - stmt.setInt(3, fp.id); + stmt.setString(1, domain.toString()); + stmt.setString(2, entity.toString()); + stmt.setString(3, fp.id); stmt.setString(4, table.toString()); stmt.setString(5, fp.value); @@ -70,16 +113,130 @@ public class MySQLInsertEntityProperties extends MySQLTransaction stmt.setString(7, fp.status); stmt.setString(8, fp.name); stmt.setString(9, fp.desc); - stmt.setString(10, fp.type); + stmt.setString(10, fp.type_id); stmt.setString(11, fp.collection); stmt.setInt(12, fp.idx); stmt.execute(); } catch (final SQLIntegrityConstraintViolationException exc) { throw new IntegrityException(exc); - } catch (final SQLException exc) { - throw new TransactionException(exc); - } catch (final ConnectionException exc) { + } catch (final SQLException | ConnectionException exc) { throw new TransactionException(exc); } } + + private void insertStages( + final List<Property> inserts, final EntityID domain, final EntityID entity) + throws TransactionException { + + for (final Property property : inserts) { + + // prepare flat property + final FlatProperty fp = new FlatProperty(); + Table table = Table.null_data; + Long unit_sig = null; + fp.id = property.getId().toString(); + fp.idx = property.getPIdx(); + fp.status = property.getStatementStatus().name(); + + if (property.getStatementStatus() == ReplacementStatus.REPLACEMENT) { + // special treatment: swap value and id. This is part of the back-end specification for the + // representation of replacement. The reason why this happens here (and + // not in the replacement class for instance) is that the original + // Property must not be changed for this. Otherwise we would have to + // change it back after the insertion or internally used replacement ids + // would be leaked. + + // value is to be the id of the property which is being replaced + fp.value = fp.id.toString(); + + // id is to be the replacement id (an internally used/private id) + fp.id = ((ReferenceValue) property.getValue()).getId().toString(); + table = Table.reference_data; + } else { + + if (property.hasUnit()) { + unit_sig = property.getUnit().getSignature(); + } + if (property.hasValue()) { + if (property.getValue() instanceof CollectionValue) { + // insert collection of values + final CollectionValue v = (CollectionValue) property.getValue(); + final Iterator<IndexedSingleValue> iterator = v.iterator(); + final SingleValue firstValue = iterator.next(); + + // insert 2nd to nth item + for (int i = 1; i < v.size(); i++) { + final SingleValue vi = iterator.next(); + fp.idx = i; + if (vi == null) { + fp.value = null; + table = Table.null_data; + } else { + fp.value = vi.toDatabaseString(); + table = vi.getTable(); + } + insertFlatProperty( + domain, entity != null ? entity : property.getDomain(), fp, table, unit_sig); + } + + // insert first item + fp.idx = 0; + if (firstValue == null) { + fp.value = null; + table = Table.null_data; + } else { + fp.value = firstValue.toDatabaseString(); + table = firstValue.getTable(); + } + + } else { + // insert single value + fp.value = ((SingleValue) property.getValue()).toDatabaseString(); + if (property instanceof Property && ((Property) property).isName()) { + table = Table.name_data; + } else { + table = ((SingleValue) property.getValue()).getTable(); + } + } + } + if (property.isNameOverride()) { + fp.name = property.getName(); + } + if (property.isDescOverride()) { + fp.desc = property.getDescription(); + } + if (property.isDatatypeOverride()) { + if (property.getDatatype() instanceof AbstractCollectionDatatype) { + fp.type_id = + ((AbstractCollectionDatatype) property.getDatatype()) + .getDatatype() + .getId() + .toString(); + fp.collection = + ((AbstractCollectionDatatype) property.getDatatype()).getCollectionName(); + } else { + fp.type_id = property.getDatatype().getId().toString(); + } + } + } + + insertFlatProperty( + domain, entity != null ? entity : property.getDomain(), fp, table, unit_sig); + } + } + + public static final String STMT_REGISTER_SUBDOMAIN = "call registerReplacementIds(?)"; + + public Deque<EntityID> registerReplacementId(final int domainCount) + throws SQLException, ConnectionException { + final PreparedStatement stmt = prepareStatement(STMT_REGISTER_SUBDOMAIN); + stmt.setInt(1, domainCount); + try (final ResultSet rs = stmt.executeQuery()) { + final Deque<EntityID> ret = new ArrayDeque<>(); + while (rs.next()) { + ret.add(new EntityID(rs.getString(1))); + } + return ret; + } + } } diff --git a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLInsertFSODescriptor.java b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLInsertFSODescriptor.java index 3c38279e6ff6896c92b7956cf4386b017cd10fe3..81b9abcc23a26338ed2fc5ead4dbcaeb0f5dc119 100644 --- a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLInsertFSODescriptor.java +++ b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLInsertFSODescriptor.java @@ -24,7 +24,7 @@ public class MySQLInsertFSODescriptor extends MySQLTransaction implements Insert try { final PreparedStatement insertFilePropsStmt = prepareStatement(STMT_INSERT_FILE_PROPERTIES); if (entity.filePath != null) { - insertFilePropsStmt.setInt(1, entity.id); + insertFilePropsStmt.setString(1, entity.id); if (entity.fileHash != null) { insertFilePropsStmt.setString(2, entity.fileHash.value); insertFilePropsStmt.setString(3, entity.fileHash.algorithm); @@ -41,9 +41,9 @@ public class MySQLInsertFSODescriptor extends MySQLTransaction implements Insert } insertFilePropsStmt.setString(6, entity.filePath.toString()); if (entity.fileParentId != null) { - insertFilePropsStmt.setInt(7, entity.fileParentId); + insertFilePropsStmt.setString(7, entity.fileParentId); } else { - insertFilePropsStmt.setNull(7, Types.INTEGER); + insertFilePropsStmt.setNull(7, Types.VARCHAR); } if (entity.fileType == ObjectType.DIRECTORY) { insertFilePropsStmt.setString(8, "inode/directory"); @@ -57,9 +57,9 @@ public class MySQLInsertFSODescriptor extends MySQLTransaction implements Insert insertFilePropsStmt.setString(9, entity.fileStorageId); insertFilePropsStmt.setString(10, entity.fileKey); if (entity.linkTarget != null) { - insertFilePropsStmt.setInt(11, entity.linkTarget); + insertFilePropsStmt.setString(11, entity.linkTarget); } else { - insertFilePropsStmt.setNull(11, Types.INTEGER); + insertFilePropsStmt.setNull(11, Types.VARCHAR); } insertFilePropsStmt.execute(); } diff --git a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLInsertLogRecord.java b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLInsertLogRecord.java deleted file mode 100644 index 6807808d18fb6ba9ce0a4ed9287ea21612cc2d2c..0000000000000000000000000000000000000000 --- a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLInsertLogRecord.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.database.backend.implementation.MySQL; - -import java.sql.PreparedStatement; -import java.sql.SQLException; -import java.util.List; -import java.util.logging.LogRecord; -import org.caosdb.server.database.access.Access; -import org.caosdb.server.database.backend.interfaces.InsertLogRecordImpl; -import org.caosdb.server.database.exceptions.TransactionException; - -public class MySQLInsertLogRecord extends MySQLTransaction implements InsertLogRecordImpl { - - public MySQLInsertLogRecord(final Access access) { - super(access); - } - - public static final String STMT_INSERT_LOG_RECORD = - "INSERT INTO logging (level, logger, message, millis, logRecord) VALUES (?,?,?,?,?)"; - - @Override - public void insert(final List<LogRecord> toBeFlushed) throws TransactionException { - try { - final PreparedStatement prepInsert = prepareStatement(STMT_INSERT_LOG_RECORD); - for (final LogRecord record : toBeFlushed) { - prepInsert.setInt(1, record.getLevel().intValue()); - prepInsert.setString(2, record.getLoggerName()); - prepInsert.setString(3, record.getMessage()); - prepInsert.setLong(4, record.getMillis()); - prepInsert.setObject(5, record); - prepInsert.executeUpdate(); - } - } catch (final SQLException e) { - throw new TransactionException(e); - } catch (final ConnectionException e) { - throw new TransactionException(e); - } - } -} diff --git a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLInsertParents.java b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLInsertParents.java index 61a5e8c48cdc85098e1b0a2f5531c358d6c6058d..0e44c687b05d4184d81f05eeb8640a15b1e88662 100644 --- a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLInsertParents.java +++ b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLInsertParents.java @@ -40,8 +40,8 @@ public class MySQLInsertParents extends MySQLTransaction implements InsertParent public void execute(final EntityID entity, final EntityID parent) throws TransactionException { try { final PreparedStatement stmt = prepareStatement(STMT_INSERT_ISA); - stmt.setInt(1, entity.toInteger()); - stmt.setInt(2, parent.toInteger()); + stmt.setString(1, entity.toString()); + stmt.setString(2, parent.toString()); stmt.execute(); } catch (final Exception e) { throw new TransactionException(e); diff --git a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLInsertSparseEntity.java b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLInsertSparseEntity.java index 1a8724a64bbbe8e77ce305cb7095506b0a9d8dd0..345dc5e91ecb83bd4e5f07817d3dce3200704cc7 100644 --- a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLInsertSparseEntity.java +++ b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLInsertSparseEntity.java @@ -1,9 +1,10 @@ /* - * ** header v3.0 * This file is a part of the CaosDB Project. * * Copyright (C) 2018 Research Group Biomedical Physics, * Max-Planck-Institute for Dynamics and Self-Organization Göttingen + * Copyright (C) 2023 IndiScale GmbH <info@indiscale.com> + * Copyright (C) 2023 Timm Fitschen <t.fitschen@indiscale.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -17,15 +18,12 @@ * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. - * - * ** end header */ package org.caosdb.server.database.backend.implementation.MySQL; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLIntegrityConstraintViolationException; -import org.caosdb.server.database.DatabaseUtils; import org.caosdb.server.database.access.Access; import org.caosdb.server.database.backend.interfaces.InsertSparseEntityImpl; import org.caosdb.server.database.exceptions.IntegrityException; @@ -38,24 +36,24 @@ public class MySQLInsertSparseEntity extends MySQLTransaction implements InsertS super(access); } - public static final String STMT_INSERT_SPARSE_ENTITY = "call insertEntity(?,?,?,?)"; + public static final String STMT_INSERT_SPARSE_ENTITY = "call insertEntity(?,?,?,?,?)"; @Override public void execute(final SparseEntity entity) { try { final PreparedStatement insertEntityStmt = prepareStatement(STMT_INSERT_SPARSE_ENTITY); - insertEntityStmt.setString(1, entity.name); - insertEntityStmt.setString(2, entity.description); - insertEntityStmt.setString(3, entity.role); - insertEntityStmt.setString(4, entity.acl); + insertEntityStmt.setString(1, entity.id); + insertEntityStmt.setString(2, entity.name); + insertEntityStmt.setString(3, entity.description); + insertEntityStmt.setString(4, entity.role); + insertEntityStmt.setString(5, entity.acl); try (final ResultSet rs = insertEntityStmt.executeQuery()) { if (rs.next()) { - entity.id = rs.getInt("EntityID"); entity.versionId = DatabaseUtils.bytes2UTF8(rs.getBytes("Version")); } else { - throw new TransactionException("Didn't get new EntityID back."); + throw new TransactionException("Didn't get the version id back."); } } diff --git a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLInsertTransactionHistory.java b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLInsertTransactionHistory.java index 0832790a0b56446570fbd7b9c3a4383390c443a7..155cb2d0b8c8db4948b4a2970785eecb636dcf9b 100644 --- a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLInsertTransactionHistory.java +++ b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLInsertTransactionHistory.java @@ -56,7 +56,7 @@ public class MySQLInsertTransactionHistory extends MySQLTransaction logStmt.setString(3, user); logStmt.setLong(4, seconds); logStmt.setInt(5, nanos); - logStmt.setInt(6, entity.toInteger()); + logStmt.setString(6, entity.toString()); logStmt.execute(); } catch (final Exception e) { throw new TransactionException(e); diff --git a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLIsSubType.java b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLIsSubType.java index f6c2daf8d4a0bf4defcaa550cf715032d57cd9cd..0d469a79482069d1f19de3931433e90faf5c667a 100644 --- a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLIsSubType.java +++ b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLIsSubType.java @@ -43,8 +43,8 @@ public class MySQLIsSubType extends MySQLTransaction implements IsSubTypeImpl { try { final PreparedStatement isSubtypeStmt = prepareStatement(STMT_IS_SUBTYPE); - isSubtypeStmt.setInt(1, child.toInteger()); - isSubtypeStmt.setInt(2, parent.toInteger()); + isSubtypeStmt.setString(1, child.toString()); + isSubtypeStmt.setString(2, parent.toString()); final ResultSet rs = isSubtypeStmt.executeQuery(); try { diff --git a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLListFiles.java b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLListFiles.java index c199c65cb18f02b4dd380b1f7af239f923e2f77b..cc087663d575149b2fc832f6c5e9cded78e554f7 100644 --- a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLListFiles.java +++ b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLListFiles.java @@ -5,7 +5,6 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.util.LinkedList; import java.util.List; -import org.caosdb.server.database.DatabaseUtils; import org.caosdb.server.database.access.Access; import org.caosdb.server.database.backend.interfaces.ListFilesImpl; import org.caosdb.server.database.exceptions.TransactionException; @@ -31,11 +30,11 @@ class LiveFSODescriptor extends FSODescriptor { // resolve here and now children = new LinkedList<VirtualFSODescriptorInterface>(); try { - pstmt.setInt(1, getEntityId().toInteger()); + pstmt.setString(1, getEntityId().toString()); try (ResultSet rs = pstmt.executeQuery()) { while (rs.next()) { final SparseEntity entity = new SparseEntity(); - entity.id = rs.getInt("FileId"); + entity.id = rs.getString("FileId"); DatabaseUtils.parseFSODescriptorFields(rs, entity); children.add(new LiveFSODescriptor(entity, pstmt)); } @@ -79,7 +78,7 @@ public class MySQLListFiles extends MySQLTransaction implements ListFilesImpl { try (ResultSet rs = stmt.executeQuery()) { while (rs.next()) { final SparseEntity entity = new SparseEntity(); - entity.id = rs.getInt("FileId"); + entity.id = rs.getString("FileId"); DatabaseUtils.parseFSODescriptorFields(rs, entity); result.add(new LiveFSODescriptor(entity, listChildrenStmt)); } diff --git a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLListUsers.java b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLListUsers.java index 83ebffe679e6625da720fd2933a5d8defb7264c3..3b68c45d0ff60473ae837f3c9ebc17ae82e68c2d 100644 --- a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLListUsers.java +++ b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLListUsers.java @@ -53,8 +53,8 @@ public class MySQLListUsers extends MySQLTransaction implements ListUsersImpl { user.name = rs.getString("name"); user.realm = rs.getString("realm"); user.email = rs.getString("email"); - user.entity = rs.getInt("entity"); - if (user.entity == 0) { + user.entity = rs.getString("entity"); + if (user.entity.isBlank() || user.entity.equals("0")) { user.entity = null; } user.status = UserStatus.valueOf(rs.getString("status")); diff --git a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLLogUserVisit.java b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLLogUserVisit.java index 23050da87e324026be1eefa02690959d0a0590cc..d3b9167e13fb92df3b69e9418f9d3c0d61458233 100644 --- a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLLogUserVisit.java +++ b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLLogUserVisit.java @@ -36,6 +36,7 @@ public class MySQLLogUserVisit extends MySQLTransaction implements LogUserVisitI public static final String LOG_USER_VISIT = "SELECT status FROM user_info WHERE realm = ? AND name = ?"; + // TODO Replace by "UPDATE user_info SET last_seen = ?"; /** Return true if this is not the first visit of this user. */ diff --git a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRegisterSubDomain.java b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRegisterSubDomain.java deleted file mode 100644 index 1e14600e71023dff544a09910fabe0dc8b17ca42..0000000000000000000000000000000000000000 --- a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRegisterSubDomain.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * ** header v3.0 - * This file is a part of the CaosDB Project. - * - * Copyright (C) 2018 Research Group Biomedical Physics, - * Max-Planck-Institute for Dynamics and Self-Organization Göttingen - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <https://www.gnu.org/licenses/>. - * - * ** end header - */ -package org.caosdb.server.database.backend.implementation.MySQL; - -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.util.ArrayDeque; -import java.util.Deque; -import org.caosdb.server.database.access.Access; -import org.caosdb.server.database.backend.interfaces.RegisterSubDomainImpl; -import org.caosdb.server.database.exceptions.TransactionException; -import org.caosdb.server.entity.EntityID; - -public class MySQLRegisterSubDomain extends MySQLTransaction implements RegisterSubDomainImpl { - - public MySQLRegisterSubDomain(final Access access) { - super(access); - } - - public static final String STMT_REGISTER_SUBDOMAIN = "call registerSubdomain(?)"; - - @Override - public Deque<EntityID> execute(final int domainCount) throws TransactionException { - try { - final PreparedStatement stmt = prepareStatement(STMT_REGISTER_SUBDOMAIN); - stmt.setInt(1, domainCount); - final ResultSet rs = stmt.executeQuery(); - try { - final Deque<EntityID> ret = new ArrayDeque<>(); - while (rs.next()) { - ret.add(new EntityID(rs.getInt(1))); - } - return ret; - } finally { - rs.close(); - } - } catch (final SQLException e) { - throw new TransactionException(e); - } catch (final ConnectionException e) { - throw new TransactionException(e); - } - } -} diff --git a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveAll.java b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveAll.java index f847bf63e9f72b4632cecf55ee4a132f141a2cd1..55b8f194ea0be90b5a77fa0c50930c836c48b3f7 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,7 +27,6 @@ import java.sql.SQLException; import java.util.LinkedList; 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; @@ -76,7 +75,7 @@ public class MySQLRetrieveAll extends MySQLTransaction implements RetrieveAllImp final String acl = DatabaseUtils.bytes2UTF8(rs.getBytes("ACL")); if (EntityACL.deserialize(acl) .isPermitted(SecurityUtils.getSubject(), EntityPermission.RETRIEVE_ENTITY)) { - ret.add(new EntityID(rs.getInt("ID"))); + ret.add(new EntityID(rs.getString("ID"))); } } return ret; diff --git a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveCurrentMaxId.java b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveCurrentMaxId.java new file mode 100644 index 0000000000000000000000000000000000000000..9f56324f84cea47081f4f9901f10c772508309d4 --- /dev/null +++ b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveCurrentMaxId.java @@ -0,0 +1,61 @@ +/* + * This file is a part of the CaosDB Project. + * + * Copyright (C) 2023 IndiScale GmbH <info@indiscale.com> + * Copyright (C) 2023 Timm Fitschen <t.fitschen@indiscale.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ +package org.caosdb.server.database.backend.implementation.MySQL; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import org.caosdb.server.database.access.Access; +import org.caosdb.server.database.backend.interfaces.RetrieveCurrentMaxIdImpl; +import org.caosdb.server.database.exceptions.TransactionException; + +/** + * Implements {@link RetrieveCurrentMaxIdImpl} for a MySQL/MariaDB back-end. + * + * @author Timm Fitschen <t.fitschen@indiscale.com> + */ +public class MySQLRetrieveCurrentMaxId extends MySQLTransaction + implements RetrieveCurrentMaxIdImpl { + + public MySQLRetrieveCurrentMaxId(Access access) { + super(access); + } + + public static final String STMT = + "SELECT max(CAST(entity_id AS UNSIGNED INT)) AS max_id FROM transaction_log WHERE transaction='Insert'"; + + @Override + public Integer execute() { + try { + try (PreparedStatement stmt = prepareStatement(STMT)) { + + try (ResultSet rs = stmt.executeQuery()) { + if (rs.next()) { + return rs.getInt("max_id"); + } else { + return 100; + } + } + } + } catch (SQLException | ConnectionException e) { + throw new TransactionException(e); + } + } +} diff --git a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveDatatypes.java b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveDatatypes.java index e085f0510a5c0a95f1e0a3bc2f702b4e345d706e..685249417ca9c1664cc1a30af3fc3a597788c29f 100644 --- a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveDatatypes.java +++ b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveDatatypes.java @@ -25,8 +25,7 @@ package org.caosdb.server.database.backend.implementation.MySQL; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; -import java.util.ArrayList; -import org.caosdb.server.database.DatabaseUtils; +import java.util.List; import org.caosdb.server.database.access.Access; import org.caosdb.server.database.backend.interfaces.RetrieveDatatypesImpl; import org.caosdb.server.database.exceptions.TransactionException; @@ -53,7 +52,7 @@ public class MySQLRetrieveDatatypes extends MySQLTransaction implements Retrieve + "FROM entities AS e WHERE e.role='DATATYPE'"; @Override - public ArrayList<VerySparseEntity> execute() throws TransactionException { + public List<VerySparseEntity> execute() throws TransactionException { try { final PreparedStatement stmt = prepareStatement(STMT_GET_DATATYPE); diff --git a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveEntityACL.java b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveEntityACL.java index 5db8ed7597b1c0d357273848fb7c36febdcfed13..716b7ff65814d1464ccac39ef64af275996e096d 100644 --- a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveEntityACL.java +++ b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveEntityACL.java @@ -39,9 +39,9 @@ public class MySQLRetrieveEntityACL extends MySQLTransaction implements Retrieve "SELECT a.acl FROM entities AS e LEFT JOIN entity_acl AS a ON (a.id = e.acl) WHERE e.id = ? LIMIT 1"; @Override - public VerySparseEntity execute(Integer id) { + public VerySparseEntity execute(String id) { try (PreparedStatement stmt = prepareStatement(STMT)) { - stmt.setInt(1, id); + stmt.setString(1, id); ResultSet rs = stmt.executeQuery(); if (rs.next()) { VerySparseEntity result = new VerySparseEntity(); diff --git a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveLogRecord.java b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveLogRecord.java deleted file mode 100644 index bba36d2eb9de29fee40f023b9b97602d5b94b8a3..0000000000000000000000000000000000000000 --- a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveLogRecord.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * ** header v3.0 - * This file is a part of the CaosDB Project. - * - * Copyright (C) 2018 Research Group Biomedical Physics, - * Max-Planck-Institute for Dynamics and Self-Organization Göttingen - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <https://www.gnu.org/licenses/>. - * - * ** end header - */ -package org.caosdb.server.database.backend.implementation.MySQL; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.ObjectInputStream; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.util.LinkedList; -import java.util.List; -import java.util.logging.Level; -import java.util.logging.LogRecord; -import org.caosdb.server.database.access.Access; -import org.caosdb.server.database.backend.interfaces.RetrieveLogRecordImpl; -import org.caosdb.server.database.exceptions.TransactionException; - -public class MySQLRetrieveLogRecord extends MySQLTransaction implements RetrieveLogRecordImpl { - - public MySQLRetrieveLogRecord(final Access access) { - super(access); - } - - public static final String STMT_RETRIEVE_LOG = "SELECT logRecord FROM logging"; - public static final String WHERE = " WHERE "; - public static final String AND = " AND "; - public static final String LOGGER_COND = "logger LIKE ?"; - public static final String MESSAGE_COND = "message LIKE ?"; - - public static final String LEVEL_COND(final Level level) { - return "level=" + level.intValue(); - } - - private static String getWhereClause( - final String logger, final Level level, final String message) { - - if (logger != null || level != null || message != null) { - final StringBuilder sb = new StringBuilder(); - - if (logger != null) { - sb.append(LOGGER_COND); - } - if (level != null) { - if (sb.length() > 0) { - sb.append(AND); - } - sb.append(LEVEL_COND(level)); - } - if (message != null) { - if (sb.length() > 0) { - sb.append(AND); - } - sb.append(MESSAGE_COND); - } - return WHERE + sb.toString(); - } - return ""; - } - - @Override - public List<LogRecord> retrieve(final String logger, final Level level, final String message) - throws TransactionException { - final List<LogRecord> ret = new LinkedList<LogRecord>(); - - final String stmtStr = STMT_RETRIEVE_LOG + getWhereClause(logger, level, message); - - try { - final PreparedStatement stmt = prepareStatement(stmtStr); - int index = 1; - if (logger != null) { - stmt.setString(index++, logger); - } - if (message != null) { - stmt.setString(index++, message); - } - ResultSet rs = null; - try { - rs = stmt.executeQuery(); - - while (rs.next()) { - final byte[] bytes = rs.getBytes("logRecord"); - - final ObjectInputStream s = new ObjectInputStream(new ByteArrayInputStream(bytes)); - final Object o = s.readObject(); - ret.add((LogRecord) o); - } - } finally { - if (rs != null && !rs.isClosed()) { - rs.close(); - } - } - } catch (final ClassNotFoundException e) { - throw new TransactionException(e); - } catch (final IOException e) { - throw new TransactionException(e); - } catch (final SQLException e) { - throw new TransactionException(e); - } catch (final ConnectionException e) { - throw new TransactionException(e); - } - - return ret; - } -} diff --git a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveParents.java b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveParents.java index 64ce7e9023bd18506a4b13610033923fa7320ca8..6f48e755537b7cd891d0254c9f2255f537a1d10a 100644 --- a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveParents.java +++ b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveParents.java @@ -26,8 +26,7 @@ import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Types; -import java.util.ArrayList; -import org.caosdb.server.database.DatabaseUtils; +import java.util.LinkedList; import org.caosdb.server.database.access.Access; import org.caosdb.server.database.backend.interfaces.RetrieveParentsImpl; import org.caosdb.server.database.exceptions.TransactionException; @@ -43,14 +42,14 @@ public class MySQLRetrieveParents extends MySQLTransaction implements RetrievePa private static final String stmtStr = "call retrieveEntityParents(?, ?)"; @Override - public ArrayList<VerySparseEntity> execute(final EntityID id, final String version) + public LinkedList<VerySparseEntity> execute(final EntityID id, final String version) throws TransactionException { try { ResultSet rs = null; try { final PreparedStatement prepareStatement = prepareStatement(stmtStr); - prepareStatement.setInt(1, id.toInteger()); + prepareStatement.setString(1, id.toString()); if (version == null) { prepareStatement.setNull(2, Types.VARBINARY); } else { diff --git a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrievePasswordValidator.java b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrievePasswordValidator.java index 1ff7dd7afd0b468a90d9e3b82faca1adc5cae84f..9fd7e0a8e441b3ede2a3447ec364c24e16898fb5 100644 --- a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrievePasswordValidator.java +++ b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrievePasswordValidator.java @@ -22,7 +22,7 @@ */ package org.caosdb.server.database.backend.implementation.MySQL; -import static org.caosdb.server.database.DatabaseUtils.bytes2UTF8; +import static org.caosdb.server.database.backend.implementation.MySQL.DatabaseUtils.bytes2UTF8; import java.security.NoSuchAlgorithmException; import java.sql.PreparedStatement; diff --git a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveProperties.java b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveProperties.java index 6bcfeac05cd3631f05615d26741c47f4c5c1bf63..97fe4dc1b2453aac7e501db796da23ae25f2e317 100644 --- a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveProperties.java +++ b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveProperties.java @@ -1,9 +1,10 @@ /* - * ** header v3.0 * This file is a part of the CaosDB Project. * * Copyright (C) 2018 Research Group Biomedical Physics, * Max-Planck-Institute for Dynamics and Self-Organization Göttingen + * Copyright (C) 2023 IndiScale GmbH <info@indiscale.com> + * Copyright (C) 2023 Timm Fitschen <t.fitschen@indiscale.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -17,25 +18,32 @@ * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. - * - * ** end header */ package org.caosdb.server.database.backend.implementation.MySQL; +import com.mysql.cj.jdbc.exceptions.MysqlDataTruncation; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Types; -import java.util.ArrayList; +import java.util.LinkedList; import java.util.List; -import org.caosdb.server.database.DatabaseUtils; import org.caosdb.server.database.access.Access; import org.caosdb.server.database.backend.interfaces.RetrievePropertiesImpl; import org.caosdb.server.database.exceptions.TransactionException; -import org.caosdb.server.database.proto.FlatProperty; import org.caosdb.server.database.proto.ProtoProperty; import org.caosdb.server.entity.EntityID; +/** + * Retrieve the entity's properties. + * + * <p>This implemation transforms the flat structure of properties which is stored in the back end + * to a deep tree of properties (if applicable) which is the reverse transformation to that + * happening in MySQLInsertEntityProperties. + * + * @see {@link MySQLInsertEntityProperties} + * @author Timm Fitschen <t.fitschen@indiscale.com> + */ public class MySQLRetrieveProperties extends MySQLTransaction implements RetrievePropertiesImpl { public MySQLRetrieveProperties(final Access access) { @@ -46,27 +54,20 @@ public class MySQLRetrieveProperties extends MySQLTransaction implements Retriev private static final String stmtStr2 = "call retrieveOverrides(?,?,?)"; @Override - public ArrayList<ProtoProperty> execute(final EntityID entity, final String version) - throws TransactionException { + public LinkedList<ProtoProperty> execute( + final EntityID entity, final String version, boolean isHead) throws TransactionException { try { final PreparedStatement prepareStatement = prepareStatement(stmtStr); - final List<FlatProperty> props = - retrieveFlatPropertiesStage1(0, entity.toInteger(), version, prepareStatement); - - final ArrayList<ProtoProperty> protos = new ArrayList<ProtoProperty>(); - for (final FlatProperty p : props) { - final ProtoProperty proto = new ProtoProperty(); - proto.property = p; - - final List<FlatProperty> subProps = - retrieveFlatPropertiesStage1(entity.toInteger(), p.id, version, prepareStatement); + final List<ProtoProperty> propsStage1 = + retrieveFlatPropertiesStage1("0", entity.toString(), version, prepareStatement); - proto.subProperties = subProps; + for (final ProtoProperty p : propsStage1) { - protos.add(proto); + p.subProperties = + retrieveFlatPropertiesStage1(entity.toString(), p.id, version, prepareStatement); } - return protos; + return DatabaseUtils.transformToDeepPropertyTree(propsStage1, isHead); } catch (final SQLException e) { throw new TransactionException(e); } catch (final ConnectionException e) { @@ -74,68 +75,61 @@ public class MySQLRetrieveProperties extends MySQLTransaction implements Retriev } } - private List<FlatProperty> retrieveFlatPropertiesStage1( - final Integer domain, - final Integer entity, - final String version, - final PreparedStatement stmt) + private List<ProtoProperty> retrieveFlatPropertiesStage1( + final String domain, final String entity, final String version, final PreparedStatement stmt) throws SQLException, ConnectionException { - ResultSet rs = null; - try { - if (domain != null && domain >= 0) { - stmt.setInt(1, domain); - } else { - stmt.setInt(1, 0); - } - stmt.setInt(2, entity); - if (version == null) { - stmt.setNull(3, Types.VARBINARY); - } else { - stmt.setString(3, version); - } + stmt.setString(1, domain); + stmt.setString(2, entity); + if (version == null) { + stmt.setNull(3, Types.VARBINARY); + } else { + stmt.setString(3, version); + } - final long t1 = System.currentTimeMillis(); - rs = stmt.executeQuery(); + final long t1 = System.currentTimeMillis(); + + try (ResultSet rs = stmt.executeQuery()) { final long t2 = System.currentTimeMillis(); addMeasurement(this.getClass().getSimpleName() + ".retrieveFlatPropertiesStage1", t2 - t1); - final List<FlatProperty> props = DatabaseUtils.parsePropertyResultset(rs); - - final PreparedStatement stmt2 = prepareStatement(stmtStr2); + final List<ProtoProperty> properties = DatabaseUtils.parsePropertyResultset(rs); - retrieveOverrides(domain, entity, version, stmt2, props); + final PreparedStatement retrieveOverrides = prepareStatement(stmtStr2); - return props; - } finally { - if (rs != null && !rs.isClosed()) { - rs.close(); + if (properties != null & !properties.isEmpty()) { + retrieveOverrides(domain, entity, version, retrieveOverrides, properties); } + + return properties; + } catch (MysqlDataTruncation e) { + throw e; } } private void retrieveOverrides( - final Integer domain, - final Integer entity, + final String domain, + final String entity, final String version, - final PreparedStatement stmt2, - final List<FlatProperty> props) + final PreparedStatement retrieveOverrides, + final List<ProtoProperty> props) throws SQLException { ResultSet rs = null; try { - stmt2.setInt(1, domain); - stmt2.setInt(2, entity); + retrieveOverrides.setString(1, domain); + retrieveOverrides.setString(2, entity); if (version == null) { - stmt2.setNull(3, Types.VARBINARY); + retrieveOverrides.setNull(3, Types.VARBINARY); } else { - stmt2.setString(3, version); + retrieveOverrides.setString(3, version); } final long t1 = System.currentTimeMillis(); - rs = stmt2.executeQuery(); + rs = retrieveOverrides.executeQuery(); final long t2 = System.currentTimeMillis(); addMeasurement(this.getClass().getSimpleName() + ".retrieveOverrides", t2 - t1); DatabaseUtils.parseOverrides(props, rs); + } finally { if (rs != null && !rs.isClosed()) { rs.close(); diff --git a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveQueryTemplateDefinition.java b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveQueryTemplateDefinition.java index 1ab95ae7afc756742475ff2f3c79eddfd632aae9..5e96353af089d469faef2ef5c970fb4de725fab0 100644 --- a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveQueryTemplateDefinition.java +++ b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveQueryTemplateDefinition.java @@ -46,7 +46,7 @@ public class MySQLRetrieveQueryTemplateDefinition extends MySQLTransaction try { final PreparedStatement stmt = prepareStatement(STMT_RETRIEVE_QUERY_TEMPLATE_DEF); - stmt.setInt(1, id.toInteger()); + stmt.setString(1, id.toString()); if (version == null) { stmt.setNull(2, Types.VARBINARY); } else { diff --git a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveSparseEntity.java b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveSparseEntity.java index ca3bd078daba4c72e7f516ce64b1da1515a1a6eb..c4a9b6262a7506c38b8dff6215682014203da1c1 100644 --- a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveSparseEntity.java +++ b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveSparseEntity.java @@ -26,7 +26,6 @@ import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Types; -import org.caosdb.server.database.DatabaseUtils; import org.caosdb.server.database.access.Access; import org.caosdb.server.database.backend.interfaces.RetrieveSparseEntityImpl; import org.caosdb.server.database.exceptions.TransactionException; @@ -52,7 +51,7 @@ public class MySQLRetrieveSparseEntity extends MySQLTransaction try { final PreparedStatement preparedStatement = prepareStatement(stmtStr); - preparedStatement.setInt(1, id.toInteger()); + preparedStatement.setString(1, id.toString()); if (version == null) { preparedStatement.setNull(2, Types.VARBINARY); } else { diff --git a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveUser.java b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveUser.java index bde1878db5d49b2da201b7d589c1ee8b8d73f656..b149fae264549f88c21d996f854662401d744beb 100644 --- a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveUser.java +++ b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveUser.java @@ -22,7 +22,7 @@ */ package org.caosdb.server.database.backend.implementation.MySQL; -import static org.caosdb.server.database.DatabaseUtils.bytes2UTF8; +import static org.caosdb.server.database.backend.implementation.MySQL.DatabaseUtils.bytes2UTF8; import java.sql.PreparedStatement; import java.sql.ResultSet; @@ -66,7 +66,7 @@ public class MySQLRetrieveUser extends MySQLTransaction implements RetrieveUserI if (rs.getString("entity") == null) { ret.entity = null; } else { - ret.entity = rs.getInt("entity"); + ret.entity = rs.getString("entity"); } } } finally { diff --git a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveVersionHistory.java b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveVersionHistory.java index 585be49afe93dc94c6ba2bc4f058c5d46fcf40f6..9493b382ec61f11953f1ba045f5c914b7502f690 100644 --- a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveVersionHistory.java +++ b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveVersionHistory.java @@ -27,7 +27,6 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.util.HashMap; import java.util.LinkedList; -import org.caosdb.server.database.DatabaseUtils; import org.caosdb.server.database.access.Access; import org.caosdb.server.database.backend.interfaces.RetrieveVersionHistoryImpl; import org.caosdb.server.database.exceptions.TransactionException; @@ -54,7 +53,7 @@ public class MySQLRetrieveVersionHistory extends MySQLTransaction final HashMap<String, VersionHistoryItem> result = new HashMap<>(); try { final PreparedStatement s = prepareStatement(VERSION_HISTORY_STMT); - s.setInt(1, entityId.toInteger()); + s.setString(1, entityId.toString()); final ResultSet rs = s.executeQuery(); while (rs.next()) { diff --git a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLSetFileCheckedTimestamp.java b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLSetFileCheckedTimestamp.java index 1e58fc9058bc2006ec8e782420add85b61aaffca..8faa3a5c82d7f1876b723a4fed6f14193f2d9158 100644 --- a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLSetFileCheckedTimestamp.java +++ b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLSetFileCheckedTimestamp.java @@ -43,7 +43,7 @@ public class MySQLSetFileCheckedTimestamp extends MySQLTransaction try { final PreparedStatement stmt = getMySQLHelper().prepareStatement(STMT_SET_TS); stmt.setLong(1, ts); - stmt.setInt(2, id.toInteger()); + stmt.setString(2, id.toString()); stmt.execute(); } catch (final SQLException e) { throw new TransactionException(e); diff --git a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLSetFileChecksum.java b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLSetFileChecksum.java index 87dbdc92afe2929a19fb89bdd9832583f9e5f1e6..e5b160c07dc47e8aa15bf50f9af8ced16cc694a5 100644 --- a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLSetFileChecksum.java +++ b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLSetFileChecksum.java @@ -1,3 +1,22 @@ +/* + * This file is a part of the CaosDB Project. + * + * Copyright (C) 2023 IndiScale GmbH <info@indiscale.com> + * Copyright (C) 2023 Timm Fitschen <t.fitschen@indiscale.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ package org.caosdb.server.database.backend.implementation.MySQL; import java.sql.PreparedStatement; @@ -11,7 +30,7 @@ import org.caosdb.server.filesystem.Hash; /** * Implements {@link SetFileChecksumImpl} for a MySQL/MariaDB back-end. * - * @author Timm Fitschen (t.fitschen@indiscale.com) + * @author Timm Fitschen <t.fitschen@indiscale.com> */ public class MySQLSetFileChecksum extends MySQLTransaction implements SetFileChecksumImpl { @@ -20,13 +39,13 @@ public class MySQLSetFileChecksum extends MySQLTransaction implements SetFileChe } public static final String STMT_SET_CHECKSUM = - "UPDATE files SET hash = unhex(?), hash_algorithm = ?, checked_timestamp = ? WHERE file_id = ?"; + "UPDATE files SET hash = unhex(?), hash_algorithm = ?, checked_timestamp = ? WHERE EXISTS (SELECT 1 FROM entity_ids AS eids WHERE eids.id = ? AND eids.internal_id = files.file_id)"; @Override public void execute(final EntityID id, final Hash hash) { try { final PreparedStatement stmt = prepareStatement(STMT_SET_CHECKSUM); - stmt.setInt(4, id.toInteger()); + stmt.setString(4, id.toString()); stmt.setString(1, hash.value); stmt.setString(2, hash.algorithm); stmt.setLong(3, hash.datetime); diff --git a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLSetQueryTemplateDefinition.java b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLSetQueryTemplateDefinition.java index f3ded949383c7d250d4ad58e6ae964aa9f96324c..70293edb974c2f12ba78caefc8474a768774e9d2 100644 --- a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLSetQueryTemplateDefinition.java +++ b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLSetQueryTemplateDefinition.java @@ -1,9 +1,10 @@ /* - * ** header v3.0 * This file is a part of the CaosDB Project. * * Copyright (C) 2018 Research Group Biomedical Physics, * Max-Planck-Institute for Dynamics and Self-Organization Göttingen + * Copyright (C) 2023 IndiScale GmbH <info@indiscale.com> + * Copyright (C) 2023 Timm Fitschen <t.fitschen@indiscale.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -17,8 +18,6 @@ * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. - * - * ** end header */ package org.caosdb.server.database.backend.implementation.MySQL; @@ -29,6 +28,11 @@ import org.caosdb.server.database.backend.interfaces.SetQueryTemplateDefinitionI import org.caosdb.server.database.exceptions.TransactionException; import org.caosdb.server.entity.EntityID; +/** + * Implements {@link SetQueryTemplateDefinitionImpl} for a MariaDB back-end. + * + * @author Timm Fitschen <t.fitschen@indiscale.com> + */ public class MySQLSetQueryTemplateDefinition extends MySQLTransaction implements SetQueryTemplateDefinitionImpl { @@ -37,13 +41,13 @@ public class MySQLSetQueryTemplateDefinition extends MySQLTransaction } public static final String STMT_INSERT_QUERY_TEMPLATE_DEF = - "INSERT INTO query_template_def (id, definition) VALUES (?,?) ON DUPLICATE KEY UPDATE definition=?;"; + "INSERT INTO query_template_def (id, definition) VALUES ((SELECT internal_id FROM entity_ids WHERE entity_ids.id = ?),?) ON DUPLICATE KEY UPDATE definition=?;"; @Override public void insert(final EntityID id, final String definition) { try { final PreparedStatement stmt = prepareStatement(STMT_INSERT_QUERY_TEMPLATE_DEF); - stmt.setInt(1, id.toInteger()); + stmt.setString(1, id.toString()); stmt.setString(2, definition); stmt.setString(3, definition); stmt.execute(); diff --git a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLUpdateSparseEntity.java b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLUpdateSparseEntity.java index df7dca93555b0140115068ebbec5233c2734415c..036a9b4fbcd6b0f6489988441a5b2df45d7a7c23 100644 --- a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLUpdateSparseEntity.java +++ b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLUpdateSparseEntity.java @@ -27,7 +27,6 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.sql.SQLIntegrityConstraintViolationException; import java.sql.Types; -import org.caosdb.server.database.DatabaseUtils; import org.caosdb.server.database.access.Access; import org.caosdb.server.database.backend.interfaces.UpdateSparseEntityImpl; import org.caosdb.server.database.exceptions.IntegrityException; @@ -50,7 +49,7 @@ public class MySQLUpdateSparseEntity extends MySQLTransaction implements UpdateS try { // file properties; final PreparedStatement updateFilePropsStmt = prepareStatement(STMT_UPDATE_FILE_PROPS); - updateFilePropsStmt.setInt(1, spe.id); // EntityID + updateFilePropsStmt.setString(1, spe.id); if (spe.filePath != null) { updateFilePropsStmt.setString(2, spe.filePath.toString()); // FilePath updateFilePropsStmt.setLong(3, spe.fileSize); // FileSize @@ -75,9 +74,9 @@ public class MySQLUpdateSparseEntity extends MySQLTransaction implements UpdateS updateFilePropsStmt.setString(7, spe.fileStorageId); // FileStorageId updateFilePropsStmt.setString(8, spe.fileKey); // FileKey if (spe.fileParentId != null) { - updateFilePropsStmt.setInt(9, spe.fileParentId); + updateFilePropsStmt.setString(9, spe.fileParentId); } else { - updateFilePropsStmt.setNull(9, Types.INTEGER); // FileParentDirectory + updateFilePropsStmt.setNull(9, Types.VARCHAR); // FileParentDirectory } } else { // move file to archive @@ -95,12 +94,12 @@ public class MySQLUpdateSparseEntity extends MySQLTransaction implements UpdateS // very sparse entity final PreparedStatement updateEntityStmt = prepareStatement(STMT_UPDATE_ENTITY); - updateEntityStmt.setInt(1, spe.id); + updateEntityStmt.setString(1, spe.id); updateEntityStmt.setString(2, spe.name); updateEntityStmt.setString(3, spe.description); updateEntityStmt.setString(4, spe.role); - if (spe.datatype != null) { - updateEntityStmt.setString(5, spe.datatype); + if (spe.datatype_id != null) { + updateEntityStmt.setString(5, spe.datatype_id); } else { updateEntityStmt.setNull(5, Types.VARCHAR); } diff --git a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLUpdateUser.java b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLUpdateUser.java index bd368698956163a8473ead7507aac0ba2b83d552..43f71e386fcad42b4a3e39e14eeedf7cb924a535 100644 --- a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLUpdateUser.java +++ b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLUpdateUser.java @@ -49,7 +49,7 @@ public class MySQLUpdateUser extends MySQLTransaction implements UpdateUserImpl stmt.setString(3, user.status.toString()); stmt.setString(4, user.email); if (user.entity != null) { - stmt.setInt(5, user.entity); + stmt.setString(5, user.entity); } else { stmt.setNull(5, Types.INTEGER); } @@ -57,7 +57,7 @@ public class MySQLUpdateUser extends MySQLTransaction implements UpdateUserImpl stmt.setString(6, user.status.toString()); stmt.setString(7, user.email); if (user.entity != null) { - stmt.setInt(8, user.entity); + stmt.setString(8, user.entity); } else { stmt.setNull(8, Types.INTEGER); } diff --git a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/Replacement.java b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/Replacement.java new file mode 100644 index 0000000000000000000000000000000000000000..c672c0eae79bc2209ce465a01b8947c64b58dfa5 --- /dev/null +++ b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/Replacement.java @@ -0,0 +1,78 @@ +/* + * This file is a part of the CaosDB Project. + * + * Copyright (C) 2023 IndiScale GmbH <info@indiscale.com> + * Copyright (C) 2023 Timm Fitschen <t.fitschen@indiscale.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ +package org.caosdb.server.database.backend.implementation.MySQL; + +import org.caosdb.server.datatype.ReferenceValue; +import org.caosdb.server.entity.EntityID; +import org.caosdb.server.entity.RetrieveEntity; +import org.caosdb.server.entity.StatementStatusInterface; +import org.caosdb.server.entity.wrapper.Property; + +enum ReplacementStatus implements StatementStatusInterface { + REPLACEMENT +} + +/** + * A wrapper of {@link Property} objects used by the back-end implementation for the MySQL/MariaDB + * back-end. + * + * <p>This class helps to transform deeply nested properties, properties with overridden data types, + * and much more to the flat (row-like) representation used internally by the back-end (which is an + * implementation detail of the back-end or part of the non-public API of the back-end from that + * point of view). + */ +public class Replacement extends Property { + + public Property replacement; + private boolean stage2Replacement; + + @Override + public EntityID getId() { + return replacement.getId(); + } + + @Override + public boolean hasId() { + return replacement.hasId(); + } + + public void setReplacementId(EntityID id) { + replacement.getId().link(id); + } + + public Replacement(Property p) { + super(p); + replacement = new Property(new RetrieveEntity()); + + replacement.setDomain(p.getDomainEntity()); + replacement.setId(new EntityID()); + replacement.setStatementStatus(ReplacementStatus.REPLACEMENT); + replacement.setValue(new ReferenceValue(p.getId())); + replacement.setPIdx(p.getPIdx()); + } + + public void setStage2Replacement(boolean t) { + this.stage2Replacement = t; + } + + public boolean isStage2Replacement() { + return stage2Replacement; + } +} diff --git a/src/main/java/org/caosdb/server/database/backend/interfaces/InsertEntityPropertiesImpl.java b/src/main/java/org/caosdb/server/database/backend/interfaces/InsertEntityPropertiesImpl.java index 2406d4895487bdec5f271b41bf46207a48c7375d..f17643fd6ff0e980aef122a318fd9fc2668e2b79 100644 --- a/src/main/java/org/caosdb/server/database/backend/interfaces/InsertEntityPropertiesImpl.java +++ b/src/main/java/org/caosdb/server/database/backend/interfaces/InsertEntityPropertiesImpl.java @@ -22,15 +22,10 @@ */ package org.caosdb.server.database.backend.interfaces; -import org.caosdb.server.database.exceptions.TransactionException; -import org.caosdb.server.database.proto.FlatProperty; -import org.caosdb.server.datatype.AbstractDatatype.Table; -import org.caosdb.server.entity.EntityID; +import org.caosdb.server.entity.EntityInterface; import org.caosdb.server.utils.Undoable; public interface InsertEntityPropertiesImpl extends BackendTransactionImpl, Undoable { - public abstract void execute( - EntityID domain, EntityID entity, FlatProperty fp, Table table, Long unit_sig) - throws TransactionException; + public abstract void execute(EntityInterface entity); } diff --git a/src/main/java/org/caosdb/server/database/backend/interfaces/InsertLogRecordImpl.java b/src/main/java/org/caosdb/server/database/backend/interfaces/RetrieveCurrentMaxIdImpl.java similarity index 64% rename from src/main/java/org/caosdb/server/database/backend/interfaces/InsertLogRecordImpl.java rename to src/main/java/org/caosdb/server/database/backend/interfaces/RetrieveCurrentMaxIdImpl.java index 78bab8e251ff6e151963f1be8caf5e73211cc8e3..ca711d52c3983b4f71bf41aa5ef63583430866a8 100644 --- a/src/main/java/org/caosdb/server/database/backend/interfaces/InsertLogRecordImpl.java +++ b/src/main/java/org/caosdb/server/database/backend/interfaces/RetrieveCurrentMaxIdImpl.java @@ -1,9 +1,8 @@ /* - * ** header v3.0 * This file is a part of the CaosDB Project. * - * Copyright (C) 2018 Research Group Biomedical Physics, - * Max-Planck-Institute for Dynamics and Self-Organization Göttingen + * Copyright (C) 2023 IndiScale GmbH <info@indiscale.com> + * Copyright (C) 2023 Timm Fitschen <t.fitschen@indiscale.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -17,16 +16,17 @@ * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. - * - * ** end header */ package org.caosdb.server.database.backend.interfaces; -import java.util.List; -import java.util.logging.LogRecord; -import org.caosdb.server.database.exceptions.TransactionException; +import org.caosdb.server.database.backend.transaction.RetrieveCurrentMaxId; -public interface InsertLogRecordImpl extends BackendTransactionImpl { +/** + * Interface for the backend implementation of {@link RetrieveCurrentMaxId} + * + * @author Timm Fitschen <t.fitschen@indiscale.com> + */ +public interface RetrieveCurrentMaxIdImpl extends BackendTransactionImpl { - public void insert(List<LogRecord> toBeFlushed) throws TransactionException; + public Integer execute(); } diff --git a/src/main/java/org/caosdb/server/database/backend/interfaces/RetrieveDatatypesImpl.java b/src/main/java/org/caosdb/server/database/backend/interfaces/RetrieveDatatypesImpl.java index 91c68dc56a31fe520d6b29c4b6780afe433f9039..0886ff4374e846acfd14e37b8d2414be912d6e3d 100644 --- a/src/main/java/org/caosdb/server/database/backend/interfaces/RetrieveDatatypesImpl.java +++ b/src/main/java/org/caosdb/server/database/backend/interfaces/RetrieveDatatypesImpl.java @@ -22,11 +22,11 @@ */ package org.caosdb.server.database.backend.interfaces; -import java.util.ArrayList; +import java.util.List; import org.caosdb.server.database.exceptions.TransactionException; import org.caosdb.server.database.proto.VerySparseEntity; public interface RetrieveDatatypesImpl extends BackendTransactionImpl { - public abstract ArrayList<VerySparseEntity> execute() throws TransactionException; + public abstract List<VerySparseEntity> execute() throws TransactionException; } diff --git a/src/main/java/org/caosdb/server/database/backend/interfaces/RetrieveEntityACLImpl.java b/src/main/java/org/caosdb/server/database/backend/interfaces/RetrieveEntityACLImpl.java index 230d4d34103603d5499532a7dc1a941cf82068e7..cc61461996dc46ae5ea4a997bd0938587959cd24 100644 --- a/src/main/java/org/caosdb/server/database/backend/interfaces/RetrieveEntityACLImpl.java +++ b/src/main/java/org/caosdb/server/database/backend/interfaces/RetrieveEntityACLImpl.java @@ -25,5 +25,5 @@ import org.caosdb.server.database.proto.VerySparseEntity; public interface RetrieveEntityACLImpl extends BackendTransactionImpl { - public VerySparseEntity execute(Integer id); + public VerySparseEntity execute(String id); } diff --git a/src/main/java/org/caosdb/server/database/backend/interfaces/RetrieveParentsImpl.java b/src/main/java/org/caosdb/server/database/backend/interfaces/RetrieveParentsImpl.java index 9bfddba10a8b548d1259adb22aac392a127a8b5e..9f7eba821e8038befa47f35b31e017aa042bb28f 100644 --- a/src/main/java/org/caosdb/server/database/backend/interfaces/RetrieveParentsImpl.java +++ b/src/main/java/org/caosdb/server/database/backend/interfaces/RetrieveParentsImpl.java @@ -22,13 +22,13 @@ */ package org.caosdb.server.database.backend.interfaces; -import java.util.ArrayList; +import java.util.LinkedList; import org.caosdb.server.database.exceptions.TransactionException; import org.caosdb.server.database.proto.VerySparseEntity; import org.caosdb.server.entity.EntityID; public interface RetrieveParentsImpl extends BackendTransactionImpl { - public ArrayList<VerySparseEntity> execute(EntityID id, String version) + public LinkedList<VerySparseEntity> execute(EntityID id, String version) throws TransactionException; } diff --git a/src/main/java/org/caosdb/server/database/backend/interfaces/RetrievePropertiesImpl.java b/src/main/java/org/caosdb/server/database/backend/interfaces/RetrievePropertiesImpl.java index dc1d5aa09653c9a4905dcc456a7037f4009bb838..6bf2051ab1ada80bd09ca074b4b50b6af3abf574 100644 --- a/src/main/java/org/caosdb/server/database/backend/interfaces/RetrievePropertiesImpl.java +++ b/src/main/java/org/caosdb/server/database/backend/interfaces/RetrievePropertiesImpl.java @@ -22,12 +22,13 @@ */ package org.caosdb.server.database.backend.interfaces; -import java.util.ArrayList; +import java.util.LinkedList; import org.caosdb.server.database.exceptions.TransactionException; import org.caosdb.server.database.proto.ProtoProperty; import org.caosdb.server.entity.EntityID; public interface RetrievePropertiesImpl extends BackendTransactionImpl { - public ArrayList<ProtoProperty> execute(EntityID id, String version) throws TransactionException; + public LinkedList<ProtoProperty> execute(EntityID id, String version, boolean isHead) + throws TransactionException; } diff --git a/src/main/java/org/caosdb/server/database/backend/transaction/InsertEntityProperties.java b/src/main/java/org/caosdb/server/database/backend/transaction/InsertEntityProperties.java index f9af6a482efaccf896a1a85fc65d0e1217bd6a53..8de8afc084ae16dea370a519a4fde3524fa8c7c9 100644 --- a/src/main/java/org/caosdb/server/database/backend/transaction/InsertEntityProperties.java +++ b/src/main/java/org/caosdb/server/database/backend/transaction/InsertEntityProperties.java @@ -22,25 +22,10 @@ */ package org.caosdb.server.database.backend.transaction; -import java.util.ArrayList; -import java.util.Deque; -import java.util.Iterator; -import java.util.List; import org.caosdb.server.database.BackendTransaction; -import org.caosdb.server.database.DatabaseUtils; import org.caosdb.server.database.backend.interfaces.InsertEntityPropertiesImpl; import org.caosdb.server.database.exceptions.TransactionException; -import org.caosdb.server.database.proto.FlatProperty; -import org.caosdb.server.datatype.AbstractCollectionDatatype; -import org.caosdb.server.datatype.AbstractDatatype.Table; -import org.caosdb.server.datatype.CollectionValue; -import org.caosdb.server.datatype.IndexedSingleValue; -import org.caosdb.server.datatype.SingleValue; -import org.caosdb.server.entity.EntityID; import org.caosdb.server.entity.EntityInterface; -import org.caosdb.server.entity.Role; -import org.caosdb.server.entity.StatementStatus; -import org.caosdb.server.entity.wrapper.Property; public class InsertEntityProperties extends BackendTransaction { @@ -54,126 +39,6 @@ public class InsertEntityProperties extends BackendTransaction { public void execute() throws TransactionException { final InsertEntityPropertiesImpl t = getImplementation(InsertEntityPropertiesImpl.class); - - final ArrayList<EntityInterface> stage1Inserts = new ArrayList<EntityInterface>(); - final ArrayList<EntityInterface> stage2Inserts = new ArrayList<EntityInterface>(); - - DatabaseUtils.deriveStage1Inserts(stage1Inserts, this.entity); - - final int domainCount = DatabaseUtils.deriveStage2Inserts(stage2Inserts, stage1Inserts); - - final Deque<EntityID> domainIds = execute(new RegisterSubDomain(domainCount)).getDomains(); - - insertStages(t, domainIds, stage1Inserts, this.entity.getDomain(), this.entity.getId()); - insertStages(t, domainIds, stage2Inserts, this.entity.getId(), null); - } - - private void insertStages( - final InsertEntityPropertiesImpl t, - final Deque<EntityID> domainIds, - final List<EntityInterface> stage1Inserts, - final EntityID domain, - final EntityID entity) - throws TransactionException { - - for (final EntityInterface property : stage1Inserts) { - if (property.hasRole() && property.getRole() == Role.Domain && !property.hasId()) { - property.setId(domainIds.removeFirst()); - } - int pIdx; - if (property instanceof Property) { - // this is a normal property - pIdx = ((Property) property).getPIdx(); - } else { - // this is a replacement - pIdx = 0; - } - - // prepare flat property - final FlatProperty fp = new FlatProperty(); - Table table = Table.null_data; - Long unit_sig = null; - fp.id = property.getId().toInteger(); - fp.idx = pIdx; - - if (property.hasReplacement()) { - if (!property.getReplacement().hasId()) { - property.getReplacement().setId(domainIds.removeFirst()); - } - - fp.value = property.getReplacement().getId().toString(); - fp.status = StatementStatus.REPLACEMENT.toString(); - - table = Table.reference_data; - } else { - if (property.hasUnit()) { - unit_sig = property.getUnit().getSignature(); - } - fp.status = property.getStatementStatus().toString(); - if (property.hasValue()) { - if (property.getValue() instanceof CollectionValue) { - // insert collection of values - final CollectionValue v = (CollectionValue) property.getValue(); - final Iterator<IndexedSingleValue> iterator = v.iterator(); - final SingleValue firstValue = iterator.next(); - - // insert 2nd to nth item - for (int i = 1; i < v.size(); i++) { - final SingleValue vi = iterator.next(); - fp.idx = i; - if (vi == null) { - fp.value = null; - table = Table.null_data; - } else { - fp.value = vi.toDatabaseString(); - table = vi.getTable(); - } - t.execute( - domain, entity != null ? entity : property.getDomain(), fp, table, unit_sig); - } - - // insert first item - fp.idx = 0; - if (firstValue == null) { - fp.value = null; - table = Table.null_data; - } else { - fp.value = firstValue.toDatabaseString(); - table = firstValue.getTable(); - } - - } else { - // insert single value - fp.value = ((SingleValue) property.getValue()).toDatabaseString(); - if (property instanceof Property && ((Property) property).isName()) { - table = Table.name_data; - } else { - table = ((SingleValue) property.getValue()).getTable(); - } - } - } - if (property.isNameOverride()) { - fp.name = property.getName(); - } - if (property.isDescOverride()) { - fp.desc = property.getDescription(); - } - if (property.isDatatypeOverride()) { - if (property.getDatatype() instanceof AbstractCollectionDatatype) { - fp.type = - ((AbstractCollectionDatatype) property.getDatatype()) - .getDatatype() - .getId() - .toString(); - fp.collection = - ((AbstractCollectionDatatype) property.getDatatype()).getCollectionName(); - } else { - fp.type = property.getDatatype().getId().toString(); - } - } - } - - t.execute(domain, entity != null ? entity : property.getDomain(), fp, table, unit_sig); - } + t.execute(this.entity); } } diff --git a/src/main/java/org/caosdb/server/database/backend/transaction/InsertEntityTransaction.java b/src/main/java/org/caosdb/server/database/backend/transaction/InsertEntityTransaction.java index 3ba121f73d41b50a7f0f604c234d8bfc3c4949ac..a6d04cb2b533606a305e8be29bbde631a88063ce 100644 --- a/src/main/java/org/caosdb/server/database/backend/transaction/InsertEntityTransaction.java +++ b/src/main/java/org/caosdb/server/database/backend/transaction/InsertEntityTransaction.java @@ -55,7 +55,9 @@ public class InsertEntityTransaction extends BackendTransaction { execute(new StoreFile(newEntity)); execute(new InsertFSODescriptor(newEntity)); } - execute(new InsertEntityProperties(newEntity)); + if (newEntity.hasProperties() || newEntity.hasValue()) { + execute(new InsertEntityProperties(newEntity)); + } execute(new InsertParents(newEntity)); if (newEntity.getEntityStatus() == EntityStatus.QUALIFIED) { diff --git a/src/main/java/org/caosdb/server/database/backend/transaction/InsertLogRecord.java b/src/main/java/org/caosdb/server/database/backend/transaction/InsertLogRecord.java deleted file mode 100644 index 3ce27add49761737c0a8365410c979665a3f16a9..0000000000000000000000000000000000000000 --- a/src/main/java/org/caosdb/server/database/backend/transaction/InsertLogRecord.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * ** header v3.0 - * This file is a part of the CaosDB Project. - * - * Copyright (C) 2018 Research Group Biomedical Physics, - * Max-Planck-Institute for Dynamics and Self-Organization Göttingen - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <https://www.gnu.org/licenses/>. - * - * ** end header - */ -package org.caosdb.server.database.backend.transaction; - -import java.util.List; -import java.util.logging.LogRecord; -import org.caosdb.server.database.BackendTransaction; -import org.caosdb.server.database.backend.interfaces.InsertLogRecordImpl; -import org.caosdb.server.database.exceptions.TransactionException; - -public class InsertLogRecord extends BackendTransaction { - - private final List<LogRecord> toBeFlushed; - - public InsertLogRecord(final List<LogRecord> toBeFlushed) { - this.toBeFlushed = toBeFlushed; - } - - @Override - protected void execute() throws TransactionException { - final InsertLogRecordImpl t = getImplementation(InsertLogRecordImpl.class); - t.insert(this.toBeFlushed); - } -} diff --git a/src/main/java/org/caosdb/server/database/backend/transaction/RegisterSubDomain.java b/src/main/java/org/caosdb/server/database/backend/transaction/RegisterSubDomain.java deleted file mode 100644 index 0321a621a82a9ae2841d31b55b05a46743f963bd..0000000000000000000000000000000000000000 --- a/src/main/java/org/caosdb/server/database/backend/transaction/RegisterSubDomain.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * ** header v3.0 - * This file is a part of the CaosDB Project. - * - * Copyright (C) 2018 Research Group Biomedical Physics, - * Max-Planck-Institute for Dynamics and Self-Organization Göttingen - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <https://www.gnu.org/licenses/>. - * - * ** end header - */ -package org.caosdb.server.database.backend.transaction; - -import java.util.Deque; -import org.caosdb.server.database.BackendTransaction; -import org.caosdb.server.database.backend.interfaces.RegisterSubDomainImpl; -import org.caosdb.server.database.exceptions.TransactionException; -import org.caosdb.server.entity.EntityID; - -public class RegisterSubDomain extends BackendTransaction { - - private final int domainCount; - private Deque<EntityID> list; - - public RegisterSubDomain(final int domainCount) { - this.domainCount = domainCount; - } - - @Override - public void execute() throws TransactionException { - final RegisterSubDomainImpl t = getImplementation(RegisterSubDomainImpl.class); - this.list = t.execute(this.domainCount); - } - - public Deque<EntityID> getDomains() { - return this.list; - } -} diff --git a/src/main/java/org/caosdb/server/database/backend/transaction/GetChildren.java b/src/main/java/org/caosdb/server/database/backend/transaction/RetrieveCurrentMaxId.java similarity index 51% rename from src/main/java/org/caosdb/server/database/backend/transaction/GetChildren.java rename to src/main/java/org/caosdb/server/database/backend/transaction/RetrieveCurrentMaxId.java index d0e2a809547ad2be4621ccab3f79d793dde70006..4dd76d30cd0cf1c63d5eb8ad48054093f064629f 100644 --- a/src/main/java/org/caosdb/server/database/backend/transaction/GetChildren.java +++ b/src/main/java/org/caosdb/server/database/backend/transaction/RetrieveCurrentMaxId.java @@ -1,9 +1,8 @@ /* - * ** header v3.0 * This file is a part of the CaosDB Project. * - * Copyright (C) 2018 Research Group Biomedical Physics, - * Max-Planck-Institute for Dynamics and Self-Organization Göttingen + * Copyright (C) 2023 IndiScale GmbH <info@indiscale.com> + * Copyright (C) 2023 Timm Fitschen <t.fitschen@indiscale.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -17,33 +16,33 @@ * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. - * - * ** end header */ package org.caosdb.server.database.backend.transaction; -import java.util.List; import org.caosdb.server.database.BackendTransaction; -import org.caosdb.server.database.backend.interfaces.GetChildrenImpl; -import org.caosdb.server.database.exceptions.TransactionException; -import org.caosdb.server.entity.EntityID; - -public class GetChildren extends BackendTransaction { +import org.caosdb.server.database.backend.interfaces.RetrieveCurrentMaxIdImpl; - private final EntityID entity; - private List<EntityID> list; +/** + * Retrieve the maximum currently known entity id. + * + * <p>This is used by the EntityIdRegistry for the legacy ids (i.e. integer ids) to generate + * sequential ids which have not been used yet. + * + * @see {@link EntityIdRegistry} + * @see {@link LegacyIds} + * @author Timm Fitschen <t.fitschen@indiscale.com> + */ +public class RetrieveCurrentMaxId extends BackendTransaction { - public GetChildren(final EntityID entity) { - this.entity = entity; - } + private Integer maxId; @Override - public void execute() throws TransactionException { - final GetChildrenImpl t = getImplementation(GetChildrenImpl.class); - this.list = t.execute(this.entity); + protected void execute() { + RetrieveCurrentMaxIdImpl t = getImplementation(RetrieveCurrentMaxIdImpl.class); + this.maxId = t.execute(); } - public List<EntityID> getList() { - return this.list; + public Integer getCurrentMaxId() { + return this.maxId; } } diff --git a/src/main/java/org/caosdb/server/database/backend/transaction/RetrieveDatatypes.java b/src/main/java/org/caosdb/server/database/backend/transaction/RetrieveDatatypes.java index b4a726da8d7b352f69df23a54371feef2d2201c5..1ca9f60e964f1e18ca49b749dde21f8e8785aaa6 100644 --- a/src/main/java/org/caosdb/server/database/backend/transaction/RetrieveDatatypes.java +++ b/src/main/java/org/caosdb/server/database/backend/transaction/RetrieveDatatypes.java @@ -22,7 +22,7 @@ */ package org.caosdb.server.database.backend.transaction; -import java.util.ArrayList; +import java.util.List; import org.caosdb.server.database.BackendTransaction; import org.caosdb.server.database.backend.interfaces.RetrieveDatatypesImpl; import org.caosdb.server.database.exceptions.TransactionException; @@ -35,7 +35,7 @@ import org.caosdb.server.entity.container.Container; public class RetrieveDatatypes extends BackendTransaction { - private ArrayList<VerySparseEntity> list; + private List<VerySparseEntity> list; @Override public void execute() throws TransactionException { diff --git a/src/main/java/org/caosdb/server/database/backend/transaction/RetrieveEntityACLTransaction.java b/src/main/java/org/caosdb/server/database/backend/transaction/RetrieveEntityACLTransaction.java index 8ef143b44e292e5b100ff30ca023e91c0adb190e..00f8c51026278d3c3660c798b5b0854aff5900eb 100644 --- a/src/main/java/org/caosdb/server/database/backend/transaction/RetrieveEntityACLTransaction.java +++ b/src/main/java/org/caosdb/server/database/backend/transaction/RetrieveEntityACLTransaction.java @@ -43,7 +43,7 @@ public class RetrieveEntityACLTransaction @Override public VerySparseEntity executeNoCache() throws TransactionException { RetrieveEntityACLImpl t = getImplementation(RetrieveEntityACLImpl.class); - return t.execute(getKey().toInteger()); + return t.execute(getKey().toString()); } @Override diff --git a/src/main/java/org/caosdb/server/database/backend/transaction/RetrieveFullEntityTransaction.java b/src/main/java/org/caosdb/server/database/backend/transaction/RetrieveFullEntityTransaction.java index eee73735a07cc2e152e0099bf5d027d1413631b4..bfb1497d71a5ede778255f6640038d647a001547 100644 --- a/src/main/java/org/caosdb/server/database/backend/transaction/RetrieveFullEntityTransaction.java +++ b/src/main/java/org/caosdb/server/database/backend/transaction/RetrieveFullEntityTransaction.java @@ -26,6 +26,7 @@ package org.caosdb.server.database.backend.transaction; import java.util.LinkedList; import java.util.List; +import org.apache.shiro.subject.Subject; import org.caosdb.server.database.BackendTransaction; import org.caosdb.server.database.exceptions.EntityDoesNotExistException; import org.caosdb.server.datatype.CollectionValue; @@ -38,6 +39,7 @@ import org.caosdb.server.entity.RetrieveEntity; import org.caosdb.server.entity.Role; import org.caosdb.server.entity.container.Container; import org.caosdb.server.entity.wrapper.Property; +import org.caosdb.server.permissions.EntityPermission; import org.caosdb.server.query.Query; import org.caosdb.server.query.Query.Selection; import org.caosdb.server.utils.EntityStatus; @@ -57,19 +59,22 @@ import org.caosdb.server.utils.EntityStatus; public class RetrieveFullEntityTransaction extends BackendTransaction { private final Container<? extends EntityInterface> container; + private final Subject subject; - public RetrieveFullEntityTransaction(final EntityInterface entity) { - final Container<EntityInterface> c = new Container<>(); - c.add(entity); - this.container = c; + @SuppressWarnings("unchecked") + public RetrieveFullEntityTransaction(final EntityInterface entity, Subject subject) { + this(new Container<>(), subject); + ((Container<EntityInterface>) this.container).add(entity); } - public RetrieveFullEntityTransaction(final Container<? extends EntityInterface> container) { + public RetrieveFullEntityTransaction( + final Container<? extends EntityInterface> container, final Subject subject) { this.container = container; + this.subject = subject; } - public RetrieveFullEntityTransaction(final EntityID id) { - this(new RetrieveEntity(id)); + public RetrieveFullEntityTransaction(final EntityID id, Subject subject) { + this(new RetrieveEntity(id), subject); } @Override @@ -160,6 +165,7 @@ public class RetrieveFullEntityTransaction extends BackendTransaction { } return false; } + /** * Return true iff the parents need to be retrieved. * @@ -219,9 +225,17 @@ public class RetrieveFullEntityTransaction extends BackendTransaction { private void resolveReferenceValue( final ReferenceValue value, final List<Selection> selections, final String propertyName) { final RetrieveEntity ref = new RetrieveEntity(value.getId()); - // recursion! (Only for the matching selections) - retrieveFullEntity(ref, getSubSelects(selections, propertyName)); - value.setEntity(ref, true); + + if (this.subject != null) { + // recursion! (Only for the matching selections) + retrieveFullEntity(ref, getSubSelects(selections, propertyName)); + + // check whether the referenced entity is readable + if (!ref.getEntityACL().isPermitted(this.subject, EntityPermission.RETRIEVE_ENTITY)) { + return; + } + value.setEntity(ref, true); + } } /** diff --git a/src/main/java/org/caosdb/server/database/backend/transaction/RetrieveLogRecord.java b/src/main/java/org/caosdb/server/database/backend/transaction/RetrieveLogRecord.java deleted file mode 100644 index eae8a96589aab3558a1978890c85bdd18bc161dc..0000000000000000000000000000000000000000 --- a/src/main/java/org/caosdb/server/database/backend/transaction/RetrieveLogRecord.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * ** header v3.0 - * This file is a part of the CaosDB Project. - * - * Copyright (C) 2018 Research Group Biomedical Physics, - * Max-Planck-Institute for Dynamics and Self-Organization Göttingen - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <https://www.gnu.org/licenses/>. - * - * ** end header - */ -package org.caosdb.server.database.backend.transaction; - -import java.util.List; -import java.util.logging.Level; -import java.util.logging.LogRecord; -import org.caosdb.server.database.BackendTransaction; -import org.caosdb.server.database.backend.interfaces.RetrieveLogRecordImpl; -import org.caosdb.server.database.exceptions.TransactionException; - -public class RetrieveLogRecord extends BackendTransaction { - - private final Level level; - private final String logger; - private List<LogRecord> logRecords; - private final String message; - - public RetrieveLogRecord(final String logger, final Level level, final String message) { - this.logger = logger; - this.level = level; - this.message = message; - } - - @Override - protected void execute() throws TransactionException { - final RetrieveLogRecordImpl t = getImplementation(RetrieveLogRecordImpl.class); - this.logRecords = t.retrieve(this.logger, this.level, this.message); - } - - public List<LogRecord> getLogRecords() { - return this.logRecords; - } -} diff --git a/src/main/java/org/caosdb/server/database/backend/transaction/RetrieveParents.java b/src/main/java/org/caosdb/server/database/backend/transaction/RetrieveParents.java index b551430a26ed6a254789258aca688521f5a57ae4..262ee271acf5b6d3652a0177b0818cbf1e9f74ee 100644 --- a/src/main/java/org/caosdb/server/database/backend/transaction/RetrieveParents.java +++ b/src/main/java/org/caosdb/server/database/backend/transaction/RetrieveParents.java @@ -24,15 +24,18 @@ */ package org.caosdb.server.database.backend.transaction; -import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; import org.apache.commons.jcs.access.behavior.ICacheAccess; import org.caosdb.server.caching.Cache; import org.caosdb.server.database.CacheableBackendTransaction; -import org.caosdb.server.database.DatabaseUtils; import org.caosdb.server.database.backend.interfaces.RetrieveParentsImpl; import org.caosdb.server.database.exceptions.TransactionException; import org.caosdb.server.database.proto.VerySparseEntity; +import org.caosdb.server.entity.EntityID; import org.caosdb.server.entity.EntityInterface; +import org.caosdb.server.entity.RetrieveEntity; +import org.caosdb.server.entity.wrapper.Parent; // TODO Problem with the caching. // When an old entity version has a parent which is deleted, the name is @@ -47,9 +50,9 @@ import org.caosdb.server.entity.EntityInterface; // See also a failing test in caosdb-pyinttest: // tests/test_version.py::test_bug_cached_parent_name_in_old_version public class RetrieveParents - extends CacheableBackendTransaction<String, ArrayList<VerySparseEntity>> { + extends CacheableBackendTransaction<String, LinkedList<VerySparseEntity>> { - private static final ICacheAccess<String, ArrayList<VerySparseEntity>> cache = + private static final ICacheAccess<String, LinkedList<VerySparseEntity>> cache = Cache.getCache("BACKEND_EntityParents"); /** @@ -71,16 +74,26 @@ public class RetrieveParents } @Override - public ArrayList<VerySparseEntity> executeNoCache() throws TransactionException { + public LinkedList<VerySparseEntity> executeNoCache() throws TransactionException { final RetrieveParentsImpl t = getImplementation(RetrieveParentsImpl.class); return t.execute(this.entity.getId(), this.entity.getVersion().getId()); } @Override - protected void process(final ArrayList<VerySparseEntity> t) throws TransactionException { + protected void process(final LinkedList<VerySparseEntity> t) throws TransactionException { this.entity.getParents().clear(); - DatabaseUtils.parseParentsFromVerySparseEntity(this.entity, t); + parseParentsFromVerySparseEntity(this.entity, t); + } + + private void parseParentsFromVerySparseEntity( + final EntityInterface entity, final List<VerySparseEntity> pars) { + for (final VerySparseEntity vsp : pars) { + final Parent p = new Parent(new RetrieveEntity(new EntityID(vsp.id))); + p.setName(vsp.name); + p.setDescription(vsp.description); + entity.addParent(p); + } } @Override diff --git a/src/main/java/org/caosdb/server/database/backend/transaction/RetrieveProperties.java b/src/main/java/org/caosdb/server/database/backend/transaction/RetrieveProperties.java index 726382f28c59a45a647098c3d227288fa1fabff6..f9c11303b31ac81696873876b532f9db95f3dbac 100644 --- a/src/main/java/org/caosdb/server/database/backend/transaction/RetrieveProperties.java +++ b/src/main/java/org/caosdb/server/database/backend/transaction/RetrieveProperties.java @@ -24,11 +24,12 @@ */ package org.caosdb.server.database.backend.transaction; -import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; import org.apache.commons.jcs.access.behavior.ICacheAccess; import org.caosdb.server.caching.Cache; import org.caosdb.server.database.CacheableBackendTransaction; -import org.caosdb.server.database.DatabaseUtils; +import org.caosdb.server.database.backend.implementation.MySQL.DatabaseUtils; import org.caosdb.server.database.backend.interfaces.RetrievePropertiesImpl; import org.caosdb.server.database.exceptions.TransactionException; import org.caosdb.server.database.proto.ProtoProperty; @@ -37,11 +38,11 @@ import org.caosdb.server.entity.Role; import org.caosdb.server.entity.wrapper.Property; public class RetrieveProperties - extends CacheableBackendTransaction<String, ArrayList<ProtoProperty>> { + extends CacheableBackendTransaction<String, LinkedList<ProtoProperty>> { private final EntityInterface entity; public static final String CACHE_REGION = "BACKEND_EntityProperties"; - private static final ICacheAccess<String, ArrayList<ProtoProperty>> cache = + private static final ICacheAccess<String, LinkedList<ProtoProperty>> cache = Cache.getCache(CACHE_REGION); /** @@ -61,40 +62,46 @@ public class RetrieveProperties } @Override - public ArrayList<ProtoProperty> executeNoCache() throws TransactionException { + public LinkedList<ProtoProperty> executeNoCache() throws TransactionException { final RetrievePropertiesImpl t = getImplementation(RetrievePropertiesImpl.class); - return t.execute(this.entity.getId(), this.entity.getVersion().getId()); + return t.execute( + this.entity.getId(), this.entity.getVersion().getId(), this.entity.getVersion().isHead()); } @Override - protected void process(final ArrayList<ProtoProperty> t) throws TransactionException { + protected void process(final LinkedList<ProtoProperty> t) throws TransactionException { this.entity.getProperties().clear(); - final ArrayList<Property> props = DatabaseUtils.parseFromProtoProperties(t); + final List<Property> props = DatabaseUtils.parseFromProtoProperties(this.entity, t); + this.entity.getProperties().addAll(props); - for (final Property p : props) { + retrieveSparseSubproperties(props); + } + + private void retrieveSparseSubproperties(List<Property> properties) { + for (final Property p : properties) { // retrieve sparse properties stage 1 + + // handle corner case where a record type is used as a property with an overridden name. + boolean isNameOverride = p.isNameOverride(); + String overrideName = p.getName(); + p.setNameOverride(false); + final RetrieveSparseEntity t1 = new RetrieveSparseEntity(p); execute(t1); + String originalName = p.getName(); + if (isNameOverride) { + p.setNameOverride(isNameOverride); + p.setName(overrideName); + } - // add default data type for record types if (!p.hasDatatype() && p.getRole() == Role.RecordType) { - p.setDatatype(p.getName()); + p.setDatatype(originalName); } // retrieve sparse properties stage 2 - for (final EntityInterface subP : p.getProperties()) { - final RetrieveSparseEntity t2 = new RetrieveSparseEntity(subP); - execute(t2); - - // add default data type for record types - if (!subP.hasDatatype() && subP.getRole() == Role.RecordType) { - subP.setDatatype(subP.getName()); - } - } + retrieveSparseSubproperties(p.getProperties()); } - - DatabaseUtils.transformToDeepPropertyTree(this.entity, props); } @Override diff --git a/src/main/java/org/caosdb/server/database/backend/transaction/RetrieveSparseEntity.java b/src/main/java/org/caosdb/server/database/backend/transaction/RetrieveSparseEntity.java index aabd490b0fb8a6b5e801f6132113726a9361d258..f1ba8354f98fb0f6716fcfe0a742a597b69dd0a3 100644 --- a/src/main/java/org/caosdb/server/database/backend/transaction/RetrieveSparseEntity.java +++ b/src/main/java/org/caosdb/server/database/backend/transaction/RetrieveSparseEntity.java @@ -27,7 +27,6 @@ package org.caosdb.server.database.backend.transaction; import org.apache.commons.jcs.access.behavior.ICacheAccess; import org.caosdb.server.caching.Cache; import org.caosdb.server.database.CacheableBackendTransaction; -import org.caosdb.server.database.DatabaseUtils; import org.caosdb.server.database.backend.interfaces.RetrieveSparseEntityImpl; import org.caosdb.server.database.exceptions.TransactionException; import org.caosdb.server.database.proto.SparseEntity; @@ -81,8 +80,10 @@ public class RetrieveSparseEntity extends CacheableBackendTransaction<String, Sp @Override protected void process(final SparseEntity t) throws TransactionException { - DatabaseUtils.parseFromSparseEntities(this.entity, t); - this.entity.setEntityStatus(EntityStatus.VALID); + if (t != null) { + this.entity.parseSparseEntity(t); + this.entity.setEntityStatus(EntityStatus.VALID); + } } @Override diff --git a/src/main/java/org/caosdb/server/database/misc/TransactionBenchmark.java b/src/main/java/org/caosdb/server/database/misc/TransactionBenchmark.java index 397a1f52201727d17b057dc92567893798b63cf5..6b14bd1d627a4dbe1b2ef131f76917586155a385 100644 --- a/src/main/java/org/caosdb/server/database/misc/TransactionBenchmark.java +++ b/src/main/java/org/caosdb/server/database/misc/TransactionBenchmark.java @@ -132,6 +132,7 @@ class RootBenchmark extends TransactionBenchmark implements ServerStat { private static final long serialVersionUID = -95212746021180579L; private transient boolean synced = false; + /** * Fetch old data (from before last shutdown) and fill it into this instance. * @@ -286,7 +287,8 @@ public abstract class TransactionBenchmark implements Serializable { protected TransactionBenchmark() { stackTraceElements = Thread.currentThread().getStackTrace(); - }; + } + ; public Iterable<SubBenchmark> getSubBenchmarks() { return this.subBenchmarks.values(); diff --git a/src/main/java/org/caosdb/server/database/proto/FlatProperty.java b/src/main/java/org/caosdb/server/database/proto/FlatProperty.java index b92d3def38d26f0f5f6cd6a0455d472b8f4f3ccb..ecb49147474e156ea3f560edc3e039f8181dd0ca 100644 --- a/src/main/java/org/caosdb/server/database/proto/FlatProperty.java +++ b/src/main/java/org/caosdb/server/database/proto/FlatProperty.java @@ -27,12 +27,13 @@ import java.io.Serializable; public class FlatProperty implements Serializable { private static final long serialVersionUID = 6039288034435124195L; - public Integer id = null; + public String id = null; public String value = null; public String status = null; public Integer idx = null; public String name = null; public String desc = null; - public String type = null; + public String type_id = null; + public String type_name = null; public String collection = null; } diff --git a/src/main/java/org/caosdb/server/database/proto/ProtoProperty.java b/src/main/java/org/caosdb/server/database/proto/ProtoProperty.java index 44acaa024513d3f4b8d89c3f59bda9baf9de3044..7a11cd4daf6369649a0205a5637d3e86fbafbffe 100644 --- a/src/main/java/org/caosdb/server/database/proto/ProtoProperty.java +++ b/src/main/java/org/caosdb/server/database/proto/ProtoProperty.java @@ -25,9 +25,9 @@ package org.caosdb.server.database.proto; import java.io.Serializable; import java.util.List; -public class ProtoProperty implements Serializable { - +public class ProtoProperty extends FlatProperty implements Serializable { private static final long serialVersionUID = 7731301985162924975L; - public List<FlatProperty> subProperties = null; - public FlatProperty property = null; + + public List<ProtoProperty> subProperties = null; + public List<Object> collValues = null; } diff --git a/src/main/java/org/caosdb/server/database/proto/ProtoUser.java b/src/main/java/org/caosdb/server/database/proto/ProtoUser.java index 643f4250216c7829c8218113a8842ff139b313ec..8b2d97a85044881d61fa6ea795443ae13f3cabc2 100644 --- a/src/main/java/org/caosdb/server/database/proto/ProtoUser.java +++ b/src/main/java/org/caosdb/server/database/proto/ProtoUser.java @@ -36,7 +36,7 @@ public class ProtoUser implements Serializable { public UserStatus status = null; public String name = null; public String email = null; - public Integer entity = null; + public String entity = null; public String realm = null; public HashSet<String> roles = null; } diff --git a/src/main/java/org/caosdb/server/database/proto/SparseEntity.java b/src/main/java/org/caosdb/server/database/proto/SparseEntity.java index 4cedd8765b65a5c45166da473e8eb680f828ee59..3ef59762982b6fe6e5fb9a94e2b2fbe0163473b1 100644 --- a/src/main/java/org/caosdb/server/database/proto/SparseEntity.java +++ b/src/main/java/org/caosdb/server/database/proto/SparseEntity.java @@ -38,19 +38,20 @@ public class SparseEntity extends VerySparseEntity { private static final long serialVersionUID = -560259468853956476L; public String collection = null; - public String datatype = null; + public String datatype_id = null; + public String datatype_name = null; public Hash fileHash = null; public Path filePath = null; public Long fileSize = null; public String fileStorageId = null; public String fileKey = null; public String fileMimeType = null; - public Integer fileParentId; + public String fileParentId; public FileSystem.ObjectType fileType = ObjectType.FILE; public String versionId = null; public Long versionSeconds = null; public Integer versionNanos = null; - public Integer linkTarget = null; + public String linkTarget = null; @Override public String toString() { @@ -60,7 +61,8 @@ public class SparseEntity extends VerySparseEntity { .append(this.description) .append(this.role) .append(this.collection) - .append(this.datatype) + .append(this.datatype_id) + .append(this.datatype_name) .append(this.fileHash.toString()) .append(this.filePath) .append(this.fileSize) diff --git a/src/main/java/org/caosdb/server/database/proto/VerySparseEntity.java b/src/main/java/org/caosdb/server/database/proto/VerySparseEntity.java index 8f32bf52038a00275568688b016b830b19fba5f0..5ceca456abc58d945599cc978987eca783637f95 100644 --- a/src/main/java/org/caosdb/server/database/proto/VerySparseEntity.java +++ b/src/main/java/org/caosdb/server/database/proto/VerySparseEntity.java @@ -35,7 +35,7 @@ public class VerySparseEntity implements Serializable { private static final long serialVersionUID = 7370925076064714740L; - public Integer id = null; + public String id = null; public String name = null; public String description = null; public String role = null; diff --git a/src/main/java/org/caosdb/server/datatype/AbstractCollectionDatatype.java b/src/main/java/org/caosdb/server/datatype/AbstractCollectionDatatype.java index cbd822ee10a24127483fae9adff440404a243771..1d816947d642e2ccb6f9867ced4478ea9a59c2d3 100644 --- a/src/main/java/org/caosdb/server/datatype/AbstractCollectionDatatype.java +++ b/src/main/java/org/caosdb/server/datatype/AbstractCollectionDatatype.java @@ -25,6 +25,7 @@ package org.caosdb.server.datatype; import java.util.HashMap; import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.caosdb.server.entity.EntityID; public abstract class AbstractCollectionDatatype extends AbstractDatatype { @@ -50,7 +51,7 @@ public abstract class AbstractCollectionDatatype extends AbstractDatatype { if (matcher.matches()) { final String col = matcher.group(1).toUpperCase(); final String type = matcher.group(2); - return collectionDatatypeFactory(col, type); + return collectionDatatypeFactory(col, null, type); } return null; } @@ -73,7 +74,9 @@ public abstract class AbstractCollectionDatatype extends AbstractDatatype { } public static AbstractCollectionDatatype collectionDatatypeFactory( - final String col, final String dt) { - return instances.get(col).createDataType(AbstractDatatype.datatypeFactory(dt)); + final String col, final String id, String name) { + return instances + .get(col) + .createDataType(AbstractDatatype.datatypeFactory(new EntityID(id), name)); } } diff --git a/src/main/java/org/caosdb/server/datatype/AbstractDatatype.java b/src/main/java/org/caosdb/server/datatype/AbstractDatatype.java index 583145fb19361adf08ae81bf8c9e338d53ad908f..795ac56dbb1df24f38964edc5eac47e491199c37 100644 --- a/src/main/java/org/caosdb/server/datatype/AbstractDatatype.java +++ b/src/main/java/org/caosdb/server/datatype/AbstractDatatype.java @@ -65,6 +65,24 @@ public abstract class AbstractDatatype { return ret; } + public static AbstractDatatype datatypeFactory(final EntityID id, String name) { + AbstractDatatype dt; + if (id.hasId()) { + dt = datatypeFactory(id); + + if (name != null) { + dt.setName(name); + } + } else { + dt = AbstractDatatype.datatypeFactory(name); + } + return dt; + } + + protected void setName(String name) { + this.name = name; + } + public static AbstractDatatype datatypeFactory(final EntityID datatype) { for (final AbstractDatatype abstractDatatype : instances.values()) { if (abstractDatatype.getId().equals(datatype)) { @@ -121,6 +139,6 @@ public abstract class AbstractDatatype { @Override public String toString() { - return "DT." + getName(); + return "DT." + (getName() != null ? getName() : getId().toString()); } } diff --git a/src/main/java/org/caosdb/server/datatype/AbstractEnumValue.java b/src/main/java/org/caosdb/server/datatype/AbstractEnumValue.java index be88002543155771d2937e216c64d52931051c25..ffe630baa1e519df2044a487ea3ad0ffc4c4bd34 100644 --- a/src/main/java/org/caosdb/server/datatype/AbstractEnumValue.java +++ b/src/main/java/org/caosdb/server/datatype/AbstractEnumValue.java @@ -19,7 +19,9 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ -/** @review Daniel Hornung 2022-03-04 */ +/** + * @review Daniel Hornung 2022-03-04 + */ package org.caosdb.server.datatype; import com.google.common.base.Objects; diff --git a/src/main/java/org/caosdb/server/datatype/CollectionValue.java b/src/main/java/org/caosdb/server/datatype/CollectionValue.java index fc94f660490284e560a34d1c407e2d74c51f6358..9fb8c6b3012dd4e9a3fa263aab149a92ce1c7643 100644 --- a/src/main/java/org/caosdb/server/datatype/CollectionValue.java +++ b/src/main/java/org/caosdb/server/datatype/CollectionValue.java @@ -21,7 +21,9 @@ * ** end header */ -/** @review Daniel Hornung 2022-03-04 */ +/** + * @review Daniel Hornung 2022-03-04 + */ package org.caosdb.server.datatype; import java.util.ArrayList; diff --git a/src/main/java/org/caosdb/server/datatype/ReferenceDatatype2.java b/src/main/java/org/caosdb/server/datatype/ReferenceDatatype2.java index 503e4679335381da6c08b746af8ed14665bd409b..cbc080b92b1220b21a36da43aecfd9434515420f 100644 --- a/src/main/java/org/caosdb/server/datatype/ReferenceDatatype2.java +++ b/src/main/java/org/caosdb/server/datatype/ReferenceDatatype2.java @@ -30,6 +30,10 @@ public class ReferenceDatatype2 extends ReferenceDatatype { private final ReferenceValue refid; + public ReferenceDatatype2(final EntityID entity) { + refid = ReferenceValue.parseReference(entity); + } + public ReferenceDatatype2(final Object entity) { try { this.refid = ReferenceValue.parseReference(entity); @@ -56,6 +60,11 @@ public class ReferenceDatatype2 extends ReferenceDatatype { return this.refid.getName(); } + @Override + protected void setName(String name) { + this.refid.setName(name); + } + @Override public boolean equals(final Object obj) { if (obj instanceof ReferenceDatatype2) { diff --git a/src/main/java/org/caosdb/server/datatype/ReferenceValue.java b/src/main/java/org/caosdb/server/datatype/ReferenceValue.java index a24e2ddfda7faf333fd7d7f64420d4a6c7fdc290..1a1c33ffe4ebafca0ca6e3011740a2d789750671 100644 --- a/src/main/java/org/caosdb/server/datatype/ReferenceValue.java +++ b/src/main/java/org/caosdb/server/datatype/ReferenceValue.java @@ -1,5 +1,4 @@ /* - * ** header v3.0 * This file is a part of the CaosDB Project. * * Copyright (C) 2018 Research Group Biomedical Physics, @@ -20,11 +19,11 @@ * * 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 */ -/** @review Daniel Hornung 2022-03-04 */ +/** + * @review Daniel Hornung 2022-03-04 + */ package org.caosdb.server.datatype; import java.util.Objects; @@ -41,7 +40,13 @@ import org.jdom2.Element; * <p>Differently from other properties, they may be versioned, i.e. they may reference to a * specific version of an entity. * - * <p>TODO: Ways to specify a reference value, what are the consequences of versioned references? + * <p>Ways to specify a reference value: {id}@{version} using either the version id (a hex'ed sha256 + * string) or "HEAD", "HEAD~1", "HEAD~2" and so on. Note: "HEAD" always means the "HEAD" + * <i>after</i> the transaction. So, if you are changing the referenced entity in the same + * transaction and you want to reference the entity in the version before the change you need to use + * "HEAD~1" because that is the old version of the future. + * + * @author Timm Fitschen <t.fitschen@indiscale.com> */ public class ReferenceValue implements SingleValue { private EntityInterface entity = null; @@ -50,6 +55,10 @@ public class ReferenceValue implements SingleValue { private String version = null; private boolean versioned = false; + public static ReferenceValue parseReference(final EntityID reference) { + return new ReferenceValue(reference); + } + public static ReferenceValue parseReference(final Object reference) throws Message { if (reference == null) { return null; @@ -65,11 +74,7 @@ public class ReferenceValue implements SingleValue { } else if (reference instanceof CollectionValue) { throw ServerMessages.DATA_TYPE_DOES_NOT_ACCEPT_COLLECTION_VALUES; } else { - try { - return new ReferenceValue(Integer.parseInt(reference.toString())); - } catch (final NumberFormatException e) { - return new ReferenceValue(reference.toString()); - } + return new ReferenceValue(reference.toString()); } } @@ -81,9 +86,9 @@ public class ReferenceValue implements SingleValue { public static ReferenceValue parseIdVersion(final String str) { final String[] split = str.split("@", 2); if (split.length == 2) { - return new ReferenceValue(Integer.parseInt(split[0]), split[1]); + return new ReferenceValue(split[0], split[1]); } else { - return new ReferenceValue(Integer.parseInt(str)); + return new ReferenceValue(str); } } @@ -150,14 +155,13 @@ public class ReferenceValue implements SingleValue { /** If the reference is given by name, versioning is not possible (at the moment). */ public ReferenceValue(final String name) { this.name = name; + this.id = new EntityID(name); } - public ReferenceValue(final int id, final String name) { - this(new EntityID(id), name); - } - - public ReferenceValue(final int id) { - this(new EntityID(id)); + public ReferenceValue(String name, String version) { + this.name = name; + this.id = new EntityID(name); + this.version = version; } public final EntityInterface getEntity() { @@ -238,4 +242,8 @@ public class ReferenceValue implements SingleValue { } return false; } + + public void setName(String name) { + this.name = name; + } } diff --git a/src/main/java/org/caosdb/server/entity/Entity.java b/src/main/java/org/caosdb/server/entity/Entity.java index 61a287e6c96a93e36409dc6ea77304c44f0516b8..7ea937ef07de61dfbc178a4c576859eca005a4ff 100644 --- a/src/main/java/org/caosdb/server/entity/Entity.java +++ b/src/main/java/org/caosdb/server/entity/Entity.java @@ -1,11 +1,10 @@ /* - * ** header v3.0 * This file is a part of the CaosDB Project. * * Copyright (C) 2018 Research Group Biomedical Physics, * Max-Planck-Institute for Dynamics and Self-Organization Göttingen - * Copyright (C) 2020 IndiScale GmbH <info@indiscale.com> - * Copyright (C) 2020 Timm Fitschen <t.fitschen@indiscale.com> + * Copyright (C) 2020 - 2023 IndiScale GmbH <info@indiscale.com> + * Copyright (C) 2020 - 2023 Timm Fitschen <t.fitschen@indiscale.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -19,8 +18,6 @@ * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. - * - * ** end header */ package org.caosdb.server.entity; @@ -47,7 +44,6 @@ import org.caosdb.server.datatype.Value; import org.caosdb.server.entity.Message.MessageType; import org.caosdb.server.entity.container.ParentContainer; import org.caosdb.server.entity.container.PropertyContainer; -import org.caosdb.server.entity.wrapper.Domain; import org.caosdb.server.entity.wrapper.Parent; import org.caosdb.server.entity.wrapper.Property; import org.caosdb.server.entity.xml.EntityToElementStrategy; @@ -70,6 +66,17 @@ import org.caosdb.server.utils.TransactionLogMessage; import org.caosdb.unit.Unit; import org.jdom2.Element; +/** + * Abstract base class for all the important entity classes. + * + * <p>This is the central data class of the server. It represents Records, RecordTypes, Properties, + * and Files. + * + * <p>This class holds the messages and other transient information as well as the actual data that + * is being retrieved from or written to the back-end during a transaction. + * + * @author Timm Fitschen <t.fitschen@indiscale.com> + */ public abstract class Entity extends AbstractObservable implements EntityInterface { public static final String DATATYPE_CHANGED_EVENT = "DatatypeChangedEvent"; @@ -81,7 +88,7 @@ public abstract class Entity extends AbstractObservable implements EntityInterfa private String name = null; private String description = null; private Value value = null; - private final EntityID domain = new EntityID(0); + private final EntityID domain = new EntityID("0"); private final EntityID id = new EntityID(); private AbstractDatatype datatype = null; private final ParentContainer parents = new ParentContainer(this); @@ -200,7 +207,6 @@ public abstract class Entity extends AbstractObservable implements EntityInterfa } else { setDatatype(AbstractDatatype.datatypeFactory(datatype)); } - notifyObservers(DATATYPE_CHANGED_EVENT); } @Override @@ -360,7 +366,7 @@ public abstract class Entity extends AbstractObservable implements EntityInterfa @Override public SparseEntity getSparseEntity() { final SparseEntity ret = new SparseEntity(); - ret.id = getId().toInteger(); + ret.id = getId().toString(); ret.name = getName(); ret.description = getDescription(); ret.acl = getEntityACL().serialize(); @@ -370,9 +376,12 @@ public abstract class Entity extends AbstractObservable implements EntityInterfa if (hasDatatype()) { if (getDatatype() instanceof AbstractCollectionDatatype) { ret.collection = ((AbstractCollectionDatatype) getDatatype()).getCollectionName(); - ret.datatype = ((AbstractCollectionDatatype) getDatatype()).getDatatype().getName(); + ret.datatype_id = + ((AbstractCollectionDatatype) getDatatype()).getDatatype().getId().toString(); + ret.datatype_name = ((AbstractCollectionDatatype) getDatatype()).getDatatype().getName(); } else { - ret.datatype = getDatatype().getName(); + ret.datatype_id = getDatatype().getId().toString(); + ret.datatype_name = getDatatype().getName(); } } if (hasFSODescriptor()) { @@ -384,10 +393,10 @@ public abstract class Entity extends AbstractObservable implements EntityInterfa ret.fileStorageId = getFSODescriptor().getFileStorageId(); ret.fileKey = getFSODescriptor().getKey(); if (getFSODescriptor().getParentDirectoryID() != null) { - ret.fileParentId = getFSODescriptor().getParentDirectoryID().toInteger(); + ret.fileParentId = getFSODescriptor().getParentDirectoryID().toString(); } if (ret.fileType == ObjectType.LINK && getFSODescriptor().getLinkTarget() != null) { - ret.linkTarget = getFSODescriptor().getLinkTarget().toInteger(); + ret.linkTarget = getFSODescriptor().getLinkTarget().toString(); } } return ret; @@ -442,7 +451,7 @@ public abstract class Entity extends AbstractObservable implements EntityInterfa } } - private StatementStatus statementStatus = null; + private StatementStatusInterface statementStatus = null; /** * statementStatus getter. @@ -450,7 +459,7 @@ public abstract class Entity extends AbstractObservable implements EntityInterfa * @return */ @Override - public StatementStatus getStatementStatus() { + public StatementStatusInterface getStatementStatus() { return this.statementStatus; } @@ -460,7 +469,7 @@ public abstract class Entity extends AbstractObservable implements EntityInterfa * @return */ @Override - public void setStatementStatus(final StatementStatus statementStatus) { + public void setStatementStatus(final StatementStatusInterface statementStatus) { this.statementStatus = statementStatus; } @@ -629,10 +638,11 @@ public abstract class Entity extends AbstractObservable implements EntityInterfa @Override public void parseValue() throws Message { - if (!this.isParsed) { - this.isParsed = true; - setValue(getDatatype().parseValue(getValue())); + if (this.isParsed) { + return; } + this.isParsed = true; + if (getValue() != null) setValue(getDatatype().parseValue(getValue())); } @Override @@ -666,13 +676,7 @@ public abstract class Entity extends AbstractObservable implements EntityInterfa // Parse ID. Generate error if it isn't an integer. if (element.getAttribute("id") != null && !element.getAttributeValue("id").equals("")) { - try { - setId(new EntityID(Integer.parseInt(element.getAttributeValue("id")))); - } catch (final NumberFormatException e) { - addInfo("Id was " + element.getAttributeValue("id") + "."); - addError(ServerMessages.PARSING_FAILED); - setEntityStatus(EntityStatus.UNQUALIFIED); - } + this.setId(element.getAttributeValue("id")); } // Parse NAME. @@ -746,13 +750,28 @@ public abstract class Entity extends AbstractObservable implements EntityInterfa // Parse sub elements which represent PROPERTIES of this // record. - final Property property = new Property(new WritableEntity(pe, Role.Property)); + WritableEntity wrapped; + if (this instanceof InsertEntity) { + wrapped = new InsertEntity(pe, Role.Property); + } else { + wrapped = new UpdateEntity(pe, Role.Property); + } + final Property property = new Property(wrapped); + + // Ignore parents of properties + property.getParents().clear(); property.setPIdx(pidx++); addProperty(property); } else if (pe.getName().equalsIgnoreCase("Parent")) { // Parse sub elements which represent PARENTS of this // record. - final Parent parent = new Parent(new WritableEntity(pe, null)); + WritableEntity wrapped; + if (this instanceof InsertEntity) { + wrapped = new InsertEntity(pe, null); + } else { + wrapped = new UpdateEntity(pe, null); + } + final Parent parent = new Parent(wrapped); addParent(parent); } else if (pe.getName().equalsIgnoreCase("EntityACL")) { // Parse and concatenate EntityACL @@ -810,10 +829,10 @@ public abstract class Entity extends AbstractObservable implements EntityInterfa } // Parse LINKTARGET - Integer linkTarget = null; + String linkTarget = null; if (element.getAttribute("linktarget") != null && !element.getAttributeValue("linktarget").isBlank()) { - linkTarget = Integer.parseInt(element.getAttributeValue("linktarget")); + linkTarget = element.getAttributeValue("linktarget"); } // Parse IMPORT @@ -887,23 +906,6 @@ public abstract class Entity extends AbstractObservable implements EntityInterfa } } - @Override - public void setReplacement(final Domain d) { - this.replacement = d; - } - - @Override - public boolean hasReplacement() { - return this.replacement != null; - } - - @Override - public Domain getReplacement() { - return this.replacement; - } - - private Domain replacement = null; - private final HashMap<String, String> flags = new HashMap<String, String>(); @Override @@ -1004,18 +1006,14 @@ public abstract class Entity extends AbstractObservable implements EntityInterfa if (!isDescOverride()) { setDescription(spe.description); } - if (!isDatatypeOverride()) { - final String dt = spe.datatype; - final String col = spe.collection; - - if (dt != null - && !dt.equalsIgnoreCase("null") - && (!hasDatatype() || !dt.equalsIgnoreCase(getDatatype().toString()))) { - if (col != null && !col.equalsIgnoreCase("null")) { - this.setDatatype(AbstractCollectionDatatype.collectionDatatypeFactory(col, dt)); - } else { - this.setDatatype(dt); - } + if (!isDatatypeOverride() && spe.datatype_id != null) { + if (spe.collection != null) { + this.setDatatype( + AbstractCollectionDatatype.collectionDatatypeFactory( + spe.collection, spe.datatype_id, spe.datatype_name)); + } else { + this.setDatatype( + AbstractDatatype.datatypeFactory(new EntityID(spe.datatype_id), spe.datatype_name)); } } @@ -1043,7 +1041,7 @@ public abstract class Entity extends AbstractObservable implements EntityInterfa @Override public boolean skipJob() { - return false; + return this.entityStatus == EntityStatus.IGNORE; } @Override diff --git a/src/main/java/org/caosdb/server/entity/EntityID.java b/src/main/java/org/caosdb/server/entity/EntityID.java index 6a2d2ca368088e54c2f3c77ae669c6feda07bb4e..5f6c4667bc3eba056a036d718a350aa032bcb8f0 100644 --- a/src/main/java/org/caosdb/server/entity/EntityID.java +++ b/src/main/java/org/caosdb/server/entity/EntityID.java @@ -1,3 +1,22 @@ +/* + * This file is a part of the CaosDB Project. + * + * Copyright (C) 2022 - 2023 IndiScale GmbH <info@indiscale.com> + * Copyright (C) 2022 - 2023 Timm Fitschen <t.fitschen@indiscale.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ package org.caosdb.server.entity; import java.io.Serializable; @@ -11,39 +30,37 @@ import java.util.Objects; * <p>It also allows to link entities together, e.g. an Entity's Property with the corresponding * abstract Property, even when the abstract Property does not have a valid ID yet (during bulk * inserts). + * + * @author Timm Fitschen <t.fitschen@indiscale.com> */ public class EntityID implements Serializable { - private static final long serialVersionUID = -9092133023135548179L; + private static final long serialVersionUID = -9062285447437546105L; - public static final EntityID DEFAULT_DOMAIN = new EntityID(0); - private Integer i = null; + public static final EntityID DEFAULT_DOMAIN = new EntityID("0"); + private String id = null; private EntityID link = null; public EntityID() {} - public EntityID(final Integer id) { - this.i = id; + public EntityID(final String id) { + this.id = id; } - public void setId(final Integer i) { - this.i = i; - } - - public Integer toInteger() { - if (this.link != null) { - return this.link.toInteger(); - } - return this.i; + public void setId(final String id) { + this.id = id; } @Override public String toString() { - return toInteger().toString(); + if (this.link != null) { + return this.link.toString(); + } + return this.id; } - boolean hasId() { - return this.i != null || this.link != null && this.link.hasId(); + public boolean hasId() { + return this.id != null || this.link != null && this.link.hasId(); } public void link(final EntityInterface entity) { @@ -57,7 +74,7 @@ public class EntityID implements Serializable { @Override public boolean equals(final Object obj) { if (obj instanceof EntityID) { - return Objects.equals(((EntityID) obj).toInteger(), this.toInteger()); + return Objects.equals(((EntityID) obj).toString(), this.toString()); } return false; } @@ -65,16 +82,20 @@ public class EntityID implements Serializable { @Override public int hashCode() { if (this.hasId()) { - return toInteger(); + return toString().hashCode(); } return super.hashCode(); } public boolean isTemporary() { - return toInteger() < 0; + return toString().startsWith("-"); } public static boolean isReserved(final EntityID id) { - return id.toInteger() < 100; + try { + return id.hasId() && Integer.parseInt(id.toString()) < 100; + } catch (NumberFormatException e) { + return false; + } } } diff --git a/src/main/java/org/caosdb/server/entity/EntityIdRegistry.java b/src/main/java/org/caosdb/server/entity/EntityIdRegistry.java new file mode 100644 index 0000000000000000000000000000000000000000..e4e1158447decf9f98ece6f4de805919b404a5c5 --- /dev/null +++ b/src/main/java/org/caosdb/server/entity/EntityIdRegistry.java @@ -0,0 +1,80 @@ +/* + * This file is a part of the CaosDB Project. + * + * Copyright (C) 2023 IndiScale GmbH <info@indiscale.com> + * Copyright (C) 2023 Timm Fitschen <t.fitschen@indiscale.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ +package org.caosdb.server.entity; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import org.caosdb.server.database.exceptions.TransactionException; +import org.caosdb.server.transaction.Transaction; + +/** + * An abstract layer for managing entity ids. + * + * <p>That is, checking whether something is a well-formed id, generating well-formed ids and more. + * + * <p>This class follows a Strategy Pattern. The actual implemenation can be found in one of the + * implementations of the {@link EntityIdRegistryStrategy} interface. + * + * @author Timm Fitschen <t.fitschen@indiscale.com> + */ +public class EntityIdRegistry { + + private EntityIdRegistryStrategy strategy; + + public EntityIdRegistry(Transaction<?> t) { + // This is the place where we will inject the new string id behavior in the + // future: + // + // this("org.caosdb.server.entity.StringIds", t); + this("org.caosdb.server.entity.LegacyIds", t); + } + + public EntityIdRegistry(EntityIdRegistryStrategy strategy) { + this.strategy = strategy; + } + + /** Return a well-formed, new and unused entity id. */ + public String generate() { + return this.strategy.generate(); + } + + public EntityIdRegistry(String strategy, Transaction<?> t) { + try { + @SuppressWarnings("unchecked") + Class<? extends EntityIdRegistry> clazz = + (Class<? extends EntityIdRegistry>) Class.forName(strategy); + Constructor<?> constructor = clazz.getConstructor(Transaction.class); + this.strategy = (EntityIdRegistryStrategy) constructor.newInstance(t); + } catch (ClassNotFoundException + | NoSuchMethodException + | SecurityException + | InstantiationException + | IllegalAccessException + | IllegalArgumentException + | InvocationTargetException e) { + throw new TransactionException(e); + } + } + + /** Return true if the id is a well-formed id. */ + public boolean matchIdPattern(String id) { + return this.strategy.matchIdPattern(id); + } +} diff --git a/src/main/java/org/caosdb/server/database/backend/interfaces/RetrieveLogRecordImpl.java b/src/main/java/org/caosdb/server/entity/EntityIdRegistryStrategy.java similarity index 52% rename from src/main/java/org/caosdb/server/database/backend/interfaces/RetrieveLogRecordImpl.java rename to src/main/java/org/caosdb/server/entity/EntityIdRegistryStrategy.java index b6646d269123d70a3b6d06a381c7a83792e1b659..e70f4f52d2e402e62d3442bfe4af91652247216b 100644 --- a/src/main/java/org/caosdb/server/database/backend/interfaces/RetrieveLogRecordImpl.java +++ b/src/main/java/org/caosdb/server/entity/EntityIdRegistryStrategy.java @@ -1,9 +1,8 @@ /* - * ** header v3.0 * This file is a part of the CaosDB Project. * - * Copyright (C) 2018 Research Group Biomedical Physics, - * Max-Planck-Institute for Dynamics and Self-Organization Göttingen + * Copyright (C) 2023 IndiScale GmbH <info@indiscale.com> + * Copyright (C) 2023 Timm Fitschen <t.fitschen@indiscale.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -17,17 +16,29 @@ * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ +package org.caosdb.server.entity; + +import org.caosdb.server.transaction.Transaction; + +/** + * Strategy interface for {@link EntityIdRegistry}. * - * ** end header + * @author Timm Fitschen <t.fitschen@indiscale.com> */ -package org.caosdb.server.database.backend.interfaces; +public abstract class EntityIdRegistryStrategy { + + private Transaction<?> transaction; + + public EntityIdRegistryStrategy(Transaction<?> transaction) { + this.transaction = transaction; + } -import java.util.List; -import java.util.logging.Level; -import java.util.logging.LogRecord; -import org.caosdb.server.database.exceptions.TransactionException; + public abstract String generate(); -public interface RetrieveLogRecordImpl extends BackendTransactionImpl { + public Transaction<?> getTransaction() { + return transaction; + } - List<LogRecord> retrieve(String logger, Level level, String message) throws TransactionException; + public abstract boolean matchIdPattern(String id); } diff --git a/src/main/java/org/caosdb/server/entity/EntityInterface.java b/src/main/java/org/caosdb/server/entity/EntityInterface.java index efeaf58c6f148ceb411d897a853776a206281167..72b84f0011eedf61ce4576c9ae4b853fa493bfa9 100644 --- a/src/main/java/org/caosdb/server/entity/EntityInterface.java +++ b/src/main/java/org/caosdb/server/entity/EntityInterface.java @@ -31,7 +31,6 @@ import org.caosdb.server.datatype.AbstractDatatype; import org.caosdb.server.datatype.Value; import org.caosdb.server.entity.container.ParentContainer; import org.caosdb.server.entity.container.PropertyContainer; -import org.caosdb.server.entity.wrapper.Domain; import org.caosdb.server.entity.wrapper.Parent; import org.caosdb.server.entity.wrapper.Property; import org.caosdb.server.entity.xml.SerializeFieldStrategy; @@ -59,9 +58,9 @@ public interface EntityInterface public abstract boolean hasId(); - public abstract StatementStatus getStatementStatus(); + public abstract StatementStatusInterface getStatementStatus(); - public abstract void setStatementStatus(StatementStatus statementStatus); + public abstract void setStatementStatus(StatementStatusInterface statementStatus); public abstract void setStatementStatus(String statementStatus); @@ -128,12 +127,6 @@ public interface EntityInterface public abstract void setProperties(PropertyContainer properties); - public abstract void setReplacement(Domain d); - - public abstract boolean hasReplacement(); - - public abstract Domain getReplacement(); - public abstract EntityInterface setDescOverride(boolean b); public abstract boolean isDescOverride(); @@ -202,4 +195,8 @@ public interface EntityInterface public abstract boolean isReferenceList(); public abstract SerializeFieldStrategy getSerializeFieldStrategy(); + + public default void setId(String id) { + getId().setId(id); + } } diff --git a/src/main/java/org/caosdb/server/entity/LegacyIds.java b/src/main/java/org/caosdb/server/entity/LegacyIds.java new file mode 100644 index 0000000000000000000000000000000000000000..0454129397b90772a505ad5b791a823eabd3ea97 --- /dev/null +++ b/src/main/java/org/caosdb/server/entity/LegacyIds.java @@ -0,0 +1,76 @@ +/* + * This file is a part of the CaosDB Project. + * + * Copyright (C) 2023 IndiScale GmbH <info@indiscale.com> + * Copyright (C) 2023 Timm Fitschen <t.fitschen@indiscale.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ +package org.caosdb.server.entity; + +import org.caosdb.server.database.backend.transaction.RetrieveCurrentMaxId; +import org.caosdb.server.transaction.Transaction; + +/** + * Implementation of {@link EntityIdRegistryStrategy} which re-implements the legacy integer-based + * entity ids. + * + * <p>This implementation will generate sequences of entity ids and matches all strings which can be + * cast to integer as well-formed entity ids. + * + * @author Timm Fitschen <t.fitschen@indiscale.com> + */ +public class LegacyIds extends EntityIdRegistryStrategy { + + private boolean isInit; + private Integer currentMaxId; + + public LegacyIds(Transaction<?> t) { + super(t); + this.isInit = false; + } + + private void init() { + isInit = true; + initCurrentMaxId(); + } + + private void initCurrentMaxId() { + this.currentMaxId = + Math.max( + 101, + getTransaction() + .execute(new RetrieveCurrentMaxId(), getTransaction().getAccess()) + .getCurrentMaxId()); + } + + @Override + public String generate() { + if (!isInit) { + init(); + } + ++currentMaxId; + return Integer.toString(++currentMaxId); + } + + @Override + public boolean matchIdPattern(String id) { + try { + Integer.parseInt(id); + return true; + } catch (NumberFormatException e) { + return false; + } + } +} diff --git a/src/main/java/org/caosdb/server/entity/MagicTypes.java b/src/main/java/org/caosdb/server/entity/MagicTypes.java index 289fae6e9164c66a8921b765a7275a0ac6a493e3..14e2c2d7ce2b809ab9a7a615a17492e53490d649 100644 --- a/src/main/java/org/caosdb/server/entity/MagicTypes.java +++ b/src/main/java/org/caosdb/server/entity/MagicTypes.java @@ -34,10 +34,10 @@ public enum MagicTypes { DESCRIPTION, ROOT_DIRECTORY; - private static final EntityID UNIT_ID = new EntityID(21); - private static final EntityID DESCRIPTION_ID = new EntityID(24); - private static final EntityID NAME_ID = new EntityID(20); - private static final EntityID ROOT_DIRECTORY_ID = new EntityID(51); + private static final EntityID UNIT_ID = new EntityID("21"); + private static final EntityID DESCRIPTION_ID = new EntityID("24"); + private static final EntityID NAME_ID = new EntityID("20"); + private static final EntityID ROOT_DIRECTORY_ID = new EntityID("51"); public EntityID getId() { switch (this) { @@ -55,14 +55,14 @@ public enum MagicTypes { } public static MagicTypes getType(final EntityID id) { - switch (id.toInteger()) { - case 21: + switch (id.toString()) { + case "21": return UNIT; - case 20: + case "20": return NAME; - case 24: + case "24": return DESCRIPTION; - case 51: + case "51": return ROOT_DIRECTORY; default: return null; @@ -88,7 +88,7 @@ public enum MagicTypes { final RetrieveContainer container = new RetrieveContainer(null, System.currentTimeMillis(), null, null); for (final MagicTypes mt : MagicTypes.values()) { - container.add(mt.getId()); + container.add(mt.getId(), null); } final Retrieve retrieve = new Retrieve(container); retrieve.execute(); diff --git a/src/main/java/org/caosdb/server/entity/RetrieveEntity.java b/src/main/java/org/caosdb/server/entity/RetrieveEntity.java index 2d1ca6f2175ca2377b7f1a73f847a48377cc4694..e1b9e8c06fbc0d14a8719bd50635464a4c117326 100644 --- a/src/main/java/org/caosdb/server/entity/RetrieveEntity.java +++ b/src/main/java/org/caosdb/server/entity/RetrieveEntity.java @@ -24,6 +24,14 @@ */ package org.caosdb.server.entity; +/** + * Entity which is to be retrieved (i.e. read-only). + * + * <p>This class only exposes those constructors which are necessary for Entity which are to be + * retrieved (e.g. Entity(id)) and hide the others (e.g. Entity(XML-Represenation)). + * + * @author Timm Fitschen <t.fitschen@indiscale.com> + */ public class RetrieveEntity extends Entity { public RetrieveEntity() { @@ -47,6 +55,11 @@ public class RetrieveEntity extends Entity { this.setVersion(new Version(version)); } + public RetrieveEntity(final EntityID id, String name, final String version) { + this(id, version); + this.setName(name); + } + public RetrieveEntity(final String name, final String version) { super(name); this.setVersion(new Version(version)); diff --git a/src/main/java/org/caosdb/server/entity/Role.java b/src/main/java/org/caosdb/server/entity/Role.java index 0e98e2c72c810dff337a476516ef8baddae9778c..cb3d9e48ae250bab46bf916af2ce99402441bddd 100644 --- a/src/main/java/org/caosdb/server/entity/Role.java +++ b/src/main/java/org/caosdb/server/entity/Role.java @@ -32,7 +32,6 @@ import org.caosdb.server.entity.xml.ToElementStrategy; public enum Role { RecordType, Record, - Domain, File, Directory, Link, diff --git a/src/main/java/org/caosdb/server/entity/StatementStatus.java b/src/main/java/org/caosdb/server/entity/StatementStatus.java index 0a7971751bffcdfc0323835433fc006bf5269ec9..83d0c57e4ece787e53efa2e149ff0910e1850ed5 100644 --- a/src/main/java/org/caosdb/server/entity/StatementStatus.java +++ b/src/main/java/org/caosdb/server/entity/StatementStatus.java @@ -33,17 +33,11 @@ package org.caosdb.server.entity; * href="../../../../../specification/RecordType.html">the documentation of RecordTypes</a> for more * information. * - * <p>2. Marking an entity as a ``REPLACEMENT`` which is needed for flat representation of deeply - * nested properties. This constant is only used for internal processes and has no meaning in the - * API. That is also the reason why this enum is not called "Importance". Apart from that, in most - * cases its meaning is identical to the importance of an entity. - * * @author Timm Fitschen (t.fitschen@indiscale.com) */ -public enum StatementStatus { +public enum StatementStatus implements StatementStatusInterface { OBLIGATORY, RECOMMENDED, SUGGESTED, FIX, - REPLACEMENT } diff --git a/src/main/java/org/caosdb/server/database/backend/interfaces/GetChildrenImpl.java b/src/main/java/org/caosdb/server/entity/StatementStatusInterface.java similarity index 58% rename from src/main/java/org/caosdb/server/database/backend/interfaces/GetChildrenImpl.java rename to src/main/java/org/caosdb/server/entity/StatementStatusInterface.java index 8b0211a0b56bec22e4823058dfa72c355e59d19d..d1596afda95a4836dcd84fc79b67cf2e0b4f12a4 100644 --- a/src/main/java/org/caosdb/server/database/backend/interfaces/GetChildrenImpl.java +++ b/src/main/java/org/caosdb/server/entity/StatementStatusInterface.java @@ -1,9 +1,8 @@ /* - * ** header v3.0 * This file is a part of the CaosDB Project. * - * Copyright (C) 2018 Research Group Biomedical Physics, - * Max-Planck-Institute for Dynamics and Self-Organization Göttingen + * Copyright (C) 2023 IndiScale GmbH <info@indiscale.com> + * Copyright (C) 2023 Timm Fitschen <t.fitschen@indiscale.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -17,16 +16,14 @@ * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. - * - * ** end header */ -package org.caosdb.server.database.backend.interfaces; - -import java.util.List; -import org.caosdb.server.database.exceptions.TransactionException; -import org.caosdb.server.entity.EntityID; +package org.caosdb.server.entity; -public interface GetChildrenImpl extends BackendTransactionImpl { - - public abstract List<EntityID> execute(EntityID entity) throws TransactionException; +/** + * Interface for the StatementStatus. This may be used by back-end implementations to use back-end + * specific implementations (e.g. + * org.caosdb.server.database.backend.implementation.MySQL.ReplacementStatus). + */ +public interface StatementStatusInterface { + public String name(); } diff --git a/src/main/java/org/caosdb/server/entity/StringIds.java b/src/main/java/org/caosdb/server/entity/StringIds.java new file mode 100644 index 0000000000000000000000000000000000000000..e1f5cb04be995253cd16cdc0a809421122b3dc8e --- /dev/null +++ b/src/main/java/org/caosdb/server/entity/StringIds.java @@ -0,0 +1,51 @@ +/* + * This file is a part of the CaosDB Project. + * + * Copyright (C) 2023 IndiScale GmbH <info@indiscale.com> + * Copyright (C) 2023 Timm Fitschen <t.fitschen@indiscale.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ +package org.caosdb.server.entity; + +import java.util.UUID; +import org.caosdb.server.transaction.Transaction; + +/** + * Example implementation for string ids based on UUID. + * + * @see {@link EntityIdRegistryStrategy} + * @author Timm Fitschen <t.fitschen@indiscale.com> + */ +public class StringIds extends EntityIdRegistryStrategy { + + public StringIds(Transaction<?> t) { + super(t); + } + + @Override + public String generate() { + return UUID.randomUUID().toString(); + } + + @Override + public boolean matchIdPattern(String id) { + try { + UUID.fromString(id); + return true; + } catch (IllegalArgumentException e) { + return false; + } + } +} diff --git a/src/main/java/org/caosdb/server/entity/WritableEntity.java b/src/main/java/org/caosdb/server/entity/WritableEntity.java index 6c967cc4e3404c7940066dc123bf805785a6e4c3..b17a54b9a68ebcdf21d8e67b9c31b0f7d59d02e8 100644 --- a/src/main/java/org/caosdb/server/entity/WritableEntity.java +++ b/src/main/java/org/caosdb/server/entity/WritableEntity.java @@ -23,7 +23,7 @@ package org.caosdb.server.entity; import org.jdom2.Element; -public class WritableEntity extends Entity { +public abstract class WritableEntity extends Entity { public WritableEntity(final Element element, Role role) { super(); diff --git a/src/main/java/org/caosdb/server/entity/container/Container.java b/src/main/java/org/caosdb/server/entity/container/Container.java index 1618fbed6a1594891ea3ddc615f48869227f2c92..fdbcbf0de68d77c3eef026ef68d559a69f2f0956 100644 --- a/src/main/java/org/caosdb/server/entity/container/Container.java +++ b/src/main/java/org/caosdb/server/entity/container/Container.java @@ -36,9 +36,11 @@ public class Container<T extends EntityInterface> extends ArrayList<T> { * Return the entity with the matching id, if it can be found inside this Container, else null. */ public T getEntityById(final EntityID id) { - for (final T e : this) { - if (e.hasId() && e.getId().equals(id)) { - return e; + if (id != null) { + for (final T e : this) { + if (e.hasId() && e.getId().equals(id)) { + return e; + } } } return null; diff --git a/src/main/java/org/caosdb/server/entity/container/EntityByIdContainer.java b/src/main/java/org/caosdb/server/entity/container/EntityByIdContainer.java index 397af673f438beba0620c3fa412ed3fd613f524b..01e1a808c4674167513f5fc77b101ed651efcf6e 100644 --- a/src/main/java/org/caosdb/server/entity/container/EntityByIdContainer.java +++ b/src/main/java/org/caosdb/server/entity/container/EntityByIdContainer.java @@ -37,7 +37,5 @@ public abstract class EntityByIdContainer extends TransactionContainer { super(user, timestamp, srid, flags); } - public abstract void add(EntityID id); - public abstract void add(EntityID id, String version); } diff --git a/src/main/java/org/caosdb/server/entity/container/RetrieveContainer.java b/src/main/java/org/caosdb/server/entity/container/RetrieveContainer.java index 24a2617865697c73ef5d8710f201930b7cac9f4e..9bce6c1ee1c37fc1e47ee4bc53d0be3e35978484 100644 --- a/src/main/java/org/caosdb/server/entity/container/RetrieveContainer.java +++ b/src/main/java/org/caosdb/server/entity/container/RetrieveContainer.java @@ -1,9 +1,10 @@ /* - * ** header v3.0 * This file is a part of the CaosDB Project. * * Copyright (C) 2018 Research Group Biomedical Physics, * Max-Planck-Institute for Dynamics and Self-Organization Göttingen + * Copyright (C) 2023 IndiScale GmbH <info@indiscale.com> + * Copyright (C) 2023 Timm Fitschen <t.fitschen@indiscale.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -17,8 +18,6 @@ * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. - * - * ** end header */ package org.caosdb.server.entity.container; @@ -27,7 +26,24 @@ import org.apache.shiro.subject.Subject; import org.caosdb.server.entity.EntityID; import org.caosdb.server.entity.RetrieveEntity; -public class RetrieveContainer extends EntityByIdContainer { +/** + * A container of entities which are about to be retrieved. + * + * <p>A suitable name would also be `ReadOnlyEntitiesContainer` because entities in this container + * will only be retrieved, not changed, inserted or deleted. + * + * <p>Because all entities are expected to be existing, all entities can theoretically be identified + * via their (valid) entity id (plus version id, if applicable). + * + * <p>However, sometimes, entities are being retrieved via the name instead of the id. Then the id + * has to be resolved first. This happens during the init stage of the owning transaction. + * + * <p>The container also holds the flags of the current transaction (e.g. cache=false, ACL=true, + * ...). + * + * @author Timm Fitschen <t.fitschen@indiscale.com> + */ +public class RetrieveContainer extends TransactionContainer { private static final long serialVersionUID = 4816050921531043503L; @@ -39,21 +55,11 @@ public class RetrieveContainer extends EntityByIdContainer { super(user, timestamp, srid, flags); } - @Override - public void add(final EntityID id) { - add(new RetrieveEntity(id)); - } - - public void add(final String name) { - add(new RetrieveEntity(name)); - } - - public void add(final String name, final String version) { - add(new RetrieveEntity(name, version)); - } - - @Override public void add(final EntityID id, final String version) { add(new RetrieveEntity(id, version)); } + + public void add(final EntityID id, final String name, final String version) { + add(new RetrieveEntity(id, name, version)); + } } diff --git a/src/main/java/org/caosdb/server/entity/container/TransactionContainer.java b/src/main/java/org/caosdb/server/entity/container/TransactionContainer.java index 89ff91d4e56574fe137968ddd344b547df442817..40525acd4ce1d768ceb4b52ec31388dc61c10a20 100644 --- a/src/main/java/org/caosdb/server/entity/container/TransactionContainer.java +++ b/src/main/java/org/caosdb/server/entity/container/TransactionContainer.java @@ -1,9 +1,10 @@ /* - * ** header v3.0 * This file is a part of the CaosDB Project. * * Copyright (C) 2018 Research Group Biomedical Physics, * Max-Planck-Institute for Dynamics and Self-Organization Göttingen + * Copyright (C) 2023 IndiScale GmbH <info@indiscale.com> + * Copyright (C) 2023 Timm Fitschen <t.fitschen@indiscale.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -17,8 +18,6 @@ * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. - * - * ** end header */ package org.caosdb.server.entity.container; @@ -39,6 +38,12 @@ import org.caosdb.server.query.Query; import org.caosdb.server.utils.EntityStatus; import org.jdom2.Element; +/** + * Base class for transaction containers used by classes such as {@link RetrieveContainer} or {@link + * WritableContainer}. + * + * @author Timm Fitschen <t.fitschen@indiscale.com> + */ public class TransactionContainer extends Container<EntityInterface> implements ToElementable, JobTarget { @@ -149,9 +154,11 @@ public class TransactionContainer extends Container<EntityInterface> * @param name */ public EntityInterface getEntityByName(final String name) { - for (final EntityInterface e : this) { - if (e.hasName() && e.getName().equals(name)) { - return e; + if (name != null) { + for (final EntityInterface e : this) { + if (e.hasName() && e.getName().equals(name)) { + return e; + } } } return null; diff --git a/src/main/java/org/caosdb/server/entity/wrapper/Domain.java b/src/main/java/org/caosdb/server/entity/wrapper/Domain.java deleted file mode 100644 index 146706efd2caa74ff1399568bf499f37683bfdbe..0000000000000000000000000000000000000000 --- a/src/main/java/org/caosdb/server/entity/wrapper/Domain.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * ** header v3.0 - * This file is a part of the CaosDB Project. - * - * Copyright (C) 2018 Research Group Biomedical Physics, - * Max-Planck-Institute for Dynamics and Self-Organization Göttingen - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <https://www.gnu.org/licenses/>. - * - * ** end header - */ -package org.caosdb.server.entity.wrapper; - -import org.caosdb.server.datatype.AbstractDatatype; -import org.caosdb.server.datatype.Value; -import org.caosdb.server.entity.Entity; -import org.caosdb.server.entity.EntityInterface; -import org.caosdb.server.entity.Role; -import org.caosdb.server.entity.container.PropertyContainer; - -public class Domain extends Entity { - - private boolean descO; - - public Domain( - final PropertyContainer properties, - final AbstractDatatype datatype, - final Value value, - final org.caosdb.server.entity.StatementStatus statementStatus) { - - setRole(Role.Domain); - setProperties(properties); - setDatatype(datatype); - setValue(value); - setStatementStatus(statementStatus); - } - - @Override - public EntityInterface setDescOverride(final boolean b) { - this.descO = b; - return this; - } - - @Override - public boolean isDescOverride() { - return this.descO; - } -} diff --git a/src/main/java/org/caosdb/server/entity/wrapper/EntityWrapper.java b/src/main/java/org/caosdb/server/entity/wrapper/EntityWrapper.java index 4e3254aabf6007bbe2a3604e2c5ed6784546c8f4..d067c42208063f94d300954bab048d6fb6695b1e 100644 --- a/src/main/java/org/caosdb/server/entity/wrapper/EntityWrapper.java +++ b/src/main/java/org/caosdb/server/entity/wrapper/EntityWrapper.java @@ -37,7 +37,7 @@ import org.caosdb.server.entity.EntityID; import org.caosdb.server.entity.EntityInterface; import org.caosdb.server.entity.Message; import org.caosdb.server.entity.Role; -import org.caosdb.server.entity.StatementStatus; +import org.caosdb.server.entity.StatementStatusInterface; import org.caosdb.server.entity.Version; import org.caosdb.server.entity.container.ParentContainer; import org.caosdb.server.entity.container.PropertyContainer; @@ -112,13 +112,13 @@ public abstract class EntityWrapper implements EntityInterface { } @Override - public StatementStatus getStatementStatus() { + public StatementStatusInterface getStatementStatus() { return this.entity.getStatementStatus(); } @Override - public void setStatementStatus(final StatementStatus statementStatus) { - this.entity.setStatementStatus(statementStatus); + public void setStatementStatus(final StatementStatusInterface replacement) { + this.entity.setStatementStatus(replacement); } @Override @@ -376,21 +376,6 @@ public abstract class EntityWrapper implements EntityInterface { this.entity.setProperties(properties); } - @Override - public void setReplacement(final Domain d) { - this.entity.setReplacement(d); - } - - @Override - public boolean hasReplacement() { - return this.entity.hasReplacement(); - } - - @Override - public Domain getReplacement() { - return this.entity.getReplacement(); - } - @Override public Map<String, String> getFlags() { return this.entity.getFlags(); diff --git a/src/main/java/org/caosdb/server/entity/wrapper/Property.java b/src/main/java/org/caosdb/server/entity/wrapper/Property.java index efa1a6857a8dec0b3d7dcc45ec689d5b3bc0f745..4d2b8e25b8e7518822b13c08ef04623e74d37a29 100644 --- a/src/main/java/org/caosdb/server/entity/wrapper/Property.java +++ b/src/main/java/org/caosdb/server/entity/wrapper/Property.java @@ -25,11 +25,14 @@ package org.caosdb.server.entity.wrapper; import org.apache.shiro.authz.AuthorizationException; import org.apache.shiro.authz.Permission; import org.apache.shiro.subject.Subject; -import org.caosdb.server.database.proto.FlatProperty; +import org.caosdb.server.database.proto.ProtoProperty; import org.caosdb.server.datatype.AbstractCollectionDatatype; +import org.caosdb.server.datatype.AbstractDatatype; +import org.caosdb.server.datatype.CollectionValue; import org.caosdb.server.datatype.GenericValue; import org.caosdb.server.entity.EntityID; import org.caosdb.server.entity.EntityInterface; +import org.caosdb.server.entity.RetrieveEntity; import org.caosdb.server.entity.xml.PropertyToElementStrategy; public class Property extends EntityWrapper { @@ -72,29 +75,46 @@ public class Property extends EntityWrapper { return this; } - public Property parseFlatProperty(final FlatProperty fp) { - setId(new EntityID(fp.id)); - setStatementStatus(fp.status); - setPIdx(fp.idx); - if (fp.name != null) { - setName(fp.name); + public Property parseProtoProperty(final ProtoProperty pp) { + setId(new EntityID(pp.id)); + setStatementStatus(pp.status); + setPIdx(pp.idx); + if (pp.name != null) { + setName(pp.name); setNameOverride(true); } - if (fp.desc != null) { - setDescription(fp.desc); + if (pp.desc != null) { + setDescription(pp.desc); setDescOverride(true); } - if (fp.type != null) { - if (fp.collection != null) { + if (pp.type_id != null) { + if (pp.collection != null) { this.setDatatype( - AbstractCollectionDatatype.collectionDatatypeFactory(fp.collection, fp.type)); + AbstractCollectionDatatype.collectionDatatypeFactory( + pp.collection, pp.type_id, pp.type_name)); } else { - this.setDatatype(fp.type); + this.setDatatype(AbstractDatatype.datatypeFactory(new EntityID(pp.type_id), pp.type_name)); } setDatatypeOverride(true); } - if (fp.value != null) { - setValue(new GenericValue(fp.value)); + if (pp.value != null) { + setValue(new GenericValue(pp.value)); + } + if (pp.collValues != null) { + CollectionValue value = new CollectionValue(); + for (Object next : pp.collValues) { + if (next == null) { + value.add(null); + } else { + value.add(new GenericValue(next.toString())); + } + } + setValue(value); + } + if (pp.subProperties != null) { + for (ProtoProperty subP : pp.subProperties) { + this.addProperty(new Property(new RetrieveEntity()).parseProtoProperty(subP)); + } } return this; } diff --git a/src/main/java/org/caosdb/server/entity/xml/EntityToElementStrategy.java b/src/main/java/org/caosdb/server/entity/xml/EntityToElementStrategy.java index 7f6e6dcae9152091b854007149b0793cbff45542..bbe1749c91e2b102a7c41e78e00cd5f02904a5ff 100644 --- a/src/main/java/org/caosdb/server/entity/xml/EntityToElementStrategy.java +++ b/src/main/java/org/caosdb/server/entity/xml/EntityToElementStrategy.java @@ -85,9 +85,7 @@ public class EntityToElementStrategy implements ToElementStrategy { // @review Florian Spreckelsen 2022-03-22 - if (entity.getEntityACL() != null) { - element.addContent(entity.getEntityACL().getPermissionsFor(SecurityUtils.getSubject())); - } + addPermissions(entity, element); if (serializeFieldStrategy.isToBeSet("id") && entity.hasId()) { element.setAttribute("id", entity.getId().toString()); } @@ -135,6 +133,12 @@ public class EntityToElementStrategy implements ToElementStrategy { } } + protected void addPermissions(EntityInterface entity, Element element) { + if (entity.getEntityACL() != null) { + element.addContent(entity.getEntityACL().getPermissionsFor(SecurityUtils.getSubject())); + } + } + /** * Set the value of the entity. * @@ -165,14 +169,16 @@ public class EntityToElementStrategy implements ToElementStrategy { // processing SELECT Queries. final EntityInterface ref = ((ReferenceValue) entity.getValue()).getEntity(); if (ref != null) { - if (entity.hasDatatype()) { - setDatatype(entity, element); - } ref.addToElement(element, serializeFieldStrategy); - // the referenced entity has been appended. Return here to suppress - // adding the reference id as well. - return; + } else { + entity.getValue().addToElement(element); + } + if (entity.hasDatatype()) { + setDatatype(entity, element); } + // the referenced entity has been appended. Return here to suppress + // adding the reference id as well. + return; } else if (entity.isReferenceList() && serializeFieldStrategy.isToBeSet("_referenced")) { // Append the all referenced entities. This needs to be done when we are // processing SELECT Queries. diff --git a/src/main/java/org/caosdb/server/entity/xml/ParentToElementStrategy.java b/src/main/java/org/caosdb/server/entity/xml/ParentToElementStrategy.java index 4f7906829427a2305f0f1b2addf72d2b69112a9e..2556fdb21bbb55252144a4200f75a265a562af74 100644 --- a/src/main/java/org/caosdb/server/entity/xml/ParentToElementStrategy.java +++ b/src/main/java/org/caosdb/server/entity/xml/ParentToElementStrategy.java @@ -26,7 +26,6 @@ package org.caosdb.server.entity.xml; import org.caosdb.server.entity.EntityInterface; import org.caosdb.server.entity.wrapper.Parent; -import org.caosdb.server.utils.EntityStatus; import org.jdom2.Element; /** @@ -51,15 +50,4 @@ public class ParentToElementStrategy extends EntityToElementStrategy { } return element; } - - @Override - public Element addToElement( - final EntityInterface entity, - final Element element, - final SerializeFieldStrategy setFieldStrategy) { - if (entity.getEntityStatus() != EntityStatus.IGNORE) { - element.addContent(toElement(entity, setFieldStrategy)); - } - return element; - } } diff --git a/src/main/java/org/caosdb/server/entity/xml/PropertyToElementStrategy.java b/src/main/java/org/caosdb/server/entity/xml/PropertyToElementStrategy.java index 23fd8a553e66df23349c20e8539e3154f3e2ac0a..7109a0d8d5312dcb7cbdecc587dc28fe0a4146ba 100644 --- a/src/main/java/org/caosdb/server/entity/xml/PropertyToElementStrategy.java +++ b/src/main/java/org/caosdb/server/entity/xml/PropertyToElementStrategy.java @@ -35,6 +35,11 @@ public class PropertyToElementStrategy extends EntityToElementStrategy { super("Property"); } + @Override + protected void addPermissions(EntityInterface entity, Element element) { + // don't + } + @Override public Element addToElement( final EntityInterface entity, diff --git a/src/main/java/org/caosdb/server/filesystem/FSODescriptor.java b/src/main/java/org/caosdb/server/filesystem/FSODescriptor.java index ee99d816bcd9585d642ea5ba3e5fe9c684cf2c06..9c4d62bdc6741671b7efd020eb0136a7d2013905 100644 --- a/src/main/java/org/caosdb/server/filesystem/FSODescriptor.java +++ b/src/main/java/org/caosdb/server/filesystem/FSODescriptor.java @@ -193,6 +193,7 @@ public class FSODescriptor implements VirtualFSODescriptorInterface { public static FSODescriptor createDir(String fileStorageId, final Path dirPath) { final FSODescriptor ret = new FSODescriptor(dirPath); + ret.key = dirPath.toString(); ret.fileStorageId = fileStorageId; ret.type = ObjectType.DIRECTORY; return ret; diff --git a/src/main/java/org/caosdb/server/filesystem/Hash.java b/src/main/java/org/caosdb/server/filesystem/Hash.java index 3b1f7085e796560904ccb7af58b7062e19b1abb7..b36e7acc88b00956298a81331eb6370be75799e9 100644 --- a/src/main/java/org/caosdb/server/filesystem/Hash.java +++ b/src/main/java/org/caosdb/server/filesystem/Hash.java @@ -86,7 +86,8 @@ public final class Hash implements Serializable { public String getAlgorithm() { return algorithm; - }; + } + ; @Override public String toString() { diff --git a/src/main/java/org/caosdb/server/filesystem/TemporaryStorage.java b/src/main/java/org/caosdb/server/filesystem/TemporaryStorage.java index ab9266382b023e7a68e86ca887f6f83abb33e50b..dbe6123716933f6bf2bd1edda98eb73ba0c0abb6 100644 --- a/src/main/java/org/caosdb/server/filesystem/TemporaryStorage.java +++ b/src/main/java/org/caosdb/server/filesystem/TemporaryStorage.java @@ -171,6 +171,7 @@ public class TemporaryStorage { return new UploadFD(tmpFile, tmpIdentifier, hash, size, tmpDir.toFile()); } + // // public TemporaryFSODescriptor link(final File target, final String srid) { // final Path tmpDir = getTmpDir(srid); diff --git a/src/main/java/org/caosdb/server/filesystem/consistency/ConsistencyEvent.java b/src/main/java/org/caosdb/server/filesystem/consistency/ConsistencyEvent.java index 373808396bcae936d4ea688f0b91c7dce974d7df..f3d13c1cb1e1c37d0dac740f37f5336e5f16ad2c 100644 --- a/src/main/java/org/caosdb/server/filesystem/consistency/ConsistencyEvent.java +++ b/src/main/java/org/caosdb/server/filesystem/consistency/ConsistencyEvent.java @@ -36,6 +36,7 @@ public class ConsistencyEvent implements ConsistencyEventInterface { public String getEventType() { return type; } + // // @Override // public ConsistencyEvent getParent() { diff --git a/src/main/java/org/caosdb/server/grpc/AccessControlManagementServiceImpl.java b/src/main/java/org/caosdb/server/grpc/AccessControlManagementServiceImpl.java index 88429828f27ac843903cfcfc801847c35eedb607..00a433d72c0a00e8e8ef796777bfa016679cbfe6 100644 --- a/src/main/java/org/caosdb/server/grpc/AccessControlManagementServiceImpl.java +++ b/src/main/java/org/caosdb/server/grpc/AccessControlManagementServiceImpl.java @@ -129,8 +129,7 @@ public class AccessControlManagementServiceImpl extends AccessControlManagementS result.setEmailSetting(EmailSetting.newBuilder().setEmail(user.email)); } if (user.entity != null) { - result.setEntitySetting( - EntitySetting.newBuilder().setEntityId(Integer.toString(user.entity))); + result.setEntitySetting(EntitySetting.newBuilder().setEntityId(user.entity)); } if (user.roles != null && !user.roles.isEmpty()) { result.addAllRoles(user.roles); diff --git a/src/main/java/org/caosdb/server/grpc/AuthInterceptor.java b/src/main/java/org/caosdb/server/grpc/AuthInterceptor.java index ce8a62210d3c8e849397600fc112cd3103f15df4..49f2facefa845a6deeed5f07bf4fde364891fcb6 100644 --- a/src/main/java/org/caosdb/server/grpc/AuthInterceptor.java +++ b/src/main/java/org/caosdb/server/grpc/AuthInterceptor.java @@ -87,6 +87,7 @@ public class AuthInterceptor implements ServerInterceptor { metadata.put(CookieSetter.SET_COOKIE, CookieSetter.EXPIRED_SESSION_COOKIE); return metadata; } + /** * A no-op listener. This class is used for failed authentications. We couldn't return a null * instead because the documentation of the {@link ServerInterceptor} explicitely forbids it. @@ -281,7 +282,7 @@ final class CookieSetter<ReqT, RespT> public void sendHeaders(Metadata headers) { setSessionCookies(headers); super.sendHeaders(headers); - }; + } private void setSessionCookies(Metadata headers) { // if authenticated as a normal user: generate and set session cookie. diff --git a/src/main/java/org/caosdb/server/grpc/CaosDBToGrpcConverters.java b/src/main/java/org/caosdb/server/grpc/CaosDBToGrpcConverters.java index 885be06c8114d999e8af467f31d5b71f4656fac0..77c0981419bccdf12396f93ea71dc8bc0c355248 100644 --- a/src/main/java/org/caosdb/server/grpc/CaosDBToGrpcConverters.java +++ b/src/main/java/org/caosdb/server/grpc/CaosDBToGrpcConverters.java @@ -74,6 +74,7 @@ import org.caosdb.server.entity.MagicTypes; import org.caosdb.server.entity.Message; import org.caosdb.server.entity.Role; import org.caosdb.server.entity.StatementStatus; +import org.caosdb.server.entity.StatementStatusInterface; import org.caosdb.server.entity.container.ParentContainer; import org.caosdb.server.entity.container.RetrieveContainer; import org.caosdb.server.entity.wrapper.Property; @@ -301,6 +302,8 @@ public class CaosDBToGrpcConverters { } else if (value instanceof GenericValue) { return convertGenericValue((GenericValue) value); + } else if (value == null) { + return ScalarValue.newBuilder().setSpecialValue(SpecialValue.SPECIAL_VALUE_UNSPECIFIED); } return null; } @@ -360,8 +363,8 @@ public class CaosDBToGrpcConverters { return convertScalarValue(v.getWrapped()).build(); } - private Importance convert(final StatementStatus statementStatus) { - switch (statementStatus) { + private Importance convert(final StatementStatusInterface statementStatus) { + switch ((StatementStatus) statementStatus) { case FIX: return Importance.IMPORTANCE_FIX; case OBLIGATORY: @@ -707,7 +710,7 @@ public class CaosDBToGrpcConverters { return null; } - private org.caosdb.api.entity.v1.Value.Builder getSelectedValue(Selection s, EntityInterface e) { + org.caosdb.api.entity.v1.Value.Builder getSelectedValue(Selection s, EntityInterface e) { org.caosdb.api.entity.v1.Value.Builder result = null; String selector = s.getSelector(); result = handleSpecialSelectors(selector, e); @@ -738,8 +741,9 @@ public class CaosDBToGrpcConverters { result = getSelectedValue(s.getSubselection(), referenced_entity); } else if (v instanceof CollectionValue) { for (Value i : (CollectionValue) v) { - if (i instanceof ReferenceValue) { - EntityInterface referenced_entity = ((ReferenceValue) i).getEntity(); + Value wrapped = ((IndexedSingleValue) i).getWrapped(); + if (wrapped instanceof ReferenceValue) { + EntityInterface referenced_entity = ((ReferenceValue) wrapped).getEntity(); result = getSelectedValue(s.getSubselection(), referenced_entity); results.add(result); } else { diff --git a/src/main/java/org/caosdb/server/grpc/EntityTransactionServiceImpl.java b/src/main/java/org/caosdb/server/grpc/EntityTransactionServiceImpl.java index 14bff252743b867823b08df53883c2bdcd7e1728..adcb4baa3abca830a1bef5493129c330219306a5 100644 --- a/src/main/java/org/caosdb/server/grpc/EntityTransactionServiceImpl.java +++ b/src/main/java/org/caosdb/server/grpc/EntityTransactionServiceImpl.java @@ -1,8 +1,8 @@ /* * This file is a part of the CaosDB Project. * - * Copyright (C) 2021 IndiScale GmbH <info@indiscale.com> - * Copyright (C) 2021 Timm Fitschen <t.fitschen@indiscale.com> + * Copyright (C) 2021,2023 IndiScale GmbH <info@indiscale.com> + * Copyright (C) 2021,2023 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 @@ -18,7 +18,6 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. * */ - package org.caosdb.server.grpc; import io.grpc.stub.StreamObserver; @@ -47,11 +46,11 @@ import org.caosdb.api.entity.v1.RetrieveResponse; import org.caosdb.api.entity.v1.SelectQueryResult; import org.caosdb.api.entity.v1.TransactionRequest; import org.caosdb.api.entity.v1.TransactionRequest.WrappedRequestsCase; -import org.caosdb.api.entity.v1.TransactionResponse; import org.caosdb.api.entity.v1.UpdateRequest; import org.caosdb.api.entity.v1.UpdateResponse; import org.caosdb.server.CaosDBException; import org.caosdb.server.entity.DeleteEntity; +import org.caosdb.server.entity.EntityID; import org.caosdb.server.entity.EntityInterface; import org.caosdb.server.entity.InsertEntity; import org.caosdb.server.entity.Message; @@ -69,6 +68,11 @@ import org.caosdb.server.transaction.UpdateACL; import org.caosdb.server.transaction.WriteTransaction; import org.caosdb.server.utils.ServerMessages; +/** + * Main entry point for the entity transaction service of the servers GRPC API. + * + * @author Timm Fitschen <t.fitschen@indiscale.com> + */ public class EntityTransactionServiceImpl extends EntityTransactionServiceImplBase { // TODO(tf) let the clients define the time zone of the date time values which are being returned. @@ -137,17 +141,13 @@ public class EntityTransactionServiceImpl extends EntityTransactionServiceImplBa } else { // or ID retrieves. final String id = sub_request.getRetrieveRequest().getId(); if (!id.isBlank()) { - try { - final RetrieveEntity entity = new RetrieveEntity(grpcToCaosdb.getId(id)); - if (isFileDownload) { - entity.setFlag(RegisterFileDownload.FLAG, RegisterFileDownload.TRUE); - } else { - entity.setFlag(RegisterFileDownload.FLAG, RegisterFileDownload.FALSE); - } - container.add(entity); - } catch (final NumberFormatException e) { - // We handle this after the retrieval + final RetrieveEntity entity = new RetrieveEntity(new EntityID(id)); + if (isFileDownload) { + entity.setFlag(RegisterFileDownload.FLAG, RegisterFileDownload.TRUE); + } else { + entity.setFlag(RegisterFileDownload.FLAG, RegisterFileDownload.FALSE); } + container.add(entity); } } } @@ -195,32 +195,9 @@ public class EntityTransactionServiceImpl extends EntityTransactionServiceImplBa } } - // Add those entities which have not been retrieved because they have a string id - for (final TransactionRequest sub_request : request.getRequestsList()) { - final String id = sub_request.getRetrieveRequest().getId(); - if (!id.isBlank()) { - try { - grpcToCaosdb.getId(id); - } catch (final NumberFormatException e) { - // ID wasn't an integer - the server doesn't support string ids yet, so that entity - // cannot exist. - builder.addResponses( - TransactionResponse.newBuilder() - .setRetrieveResponse( - RetrieveResponse.newBuilder().setEntityResponse(entityDoesNotExist(id)))); - } - } - } return builder.build(); } - private EntityResponse entityDoesNotExist(final String id) { - return EntityResponse.newBuilder() - .addErrors(caosdbToGrpc.convert(ServerMessages.ENTITY_DOES_NOT_EXIST)) - .setEntity(Entity.newBuilder().setId(id)) - .build(); - } - private String getSRID() { return UUID.randomUUID().toString(); } @@ -297,29 +274,16 @@ public class EntityTransactionServiceImpl extends EntityTransactionServiceImplBa final UpdateRequest updateRequest = subRequest.getUpdateRequest(); final Entity updateEntity = updateRequest.getEntityRequest().getEntity(); - try { - final UpdateEntity entity = - new UpdateEntity( - grpcToCaosdb.getId(updateEntity.getId()), // ID is not handled by grpc convert - grpcToCaosdb.convert(updateEntity.getRole())); - grpcToCaosdb.convert(updateEntity, entity); - addFileUpload(container, entity, updateRequest.getEntityRequest()); - container.add(entity); - } catch (final NumberFormatException e) { - // ID wasn't an integer - return failedWriteDueToStringId(requests); - } + final UpdateEntity entity = + new UpdateEntity( + new EntityID(updateEntity.getId()), grpcToCaosdb.convert(updateEntity.getRole())); + grpcToCaosdb.convert(updateEntity, entity); + addFileUpload(container, entity, updateRequest.getEntityRequest()); + container.add(entity); break; case DELETE_REQUEST: final DeleteRequest deleteRequest = subRequest.getDeleteRequest(); - try { - final DeleteEntity entity = new DeleteEntity(grpcToCaosdb.getId(deleteRequest.getId())); - container.add(entity); - - } catch (final NumberFormatException e) { - // ID wasn't an integer - return failedWriteDueToStringId(requests); - } + container.add(new DeleteEntity(new EntityID(deleteRequest.getId()))); break; default: throw new CaosDBException( @@ -359,65 +323,6 @@ public class EntityTransactionServiceImpl extends EntityTransactionServiceImplBa return builder.build(); } - /** - * Handle a request which contains string id (which cannot be converted to integer ids) and return - * a response which has the "ENTITY_DOES_NOT_EXIST" error for all entities with affected ids. - * - * <p>This does not attempt to execute a single request. - * - * @param request - * @return - */ - private MultiTransactionResponse failedWriteDueToStringId(final MultiTransactionRequest request) { - final org.caosdb.api.entity.v1.MultiTransactionResponse.Builder builder = - MultiTransactionResponse.newBuilder(); - for (final TransactionRequest subRequest : request.getRequestsList()) { - final IdResponse.Builder idResponse = IdResponse.newBuilder(); - switch (subRequest.getWrappedRequestsCase()) { - case INSERT_REQUEST: - builder - .addResponsesBuilder() - .setInsertResponse(InsertResponse.newBuilder().setIdResponse(idResponse)); - - break; - case UPDATE_REQUEST: - final UpdateRequest updateRequest = subRequest.getUpdateRequest(); - final Entity updateEntity = updateRequest.getEntityRequest().getEntity(); - - idResponse.setId(updateEntity.getId()); - try { - grpcToCaosdb.getId(updateEntity.getId()); - } catch (final NumberFormatException e) { - // ID wasn't an integer - idResponse.addErrors(caosdbToGrpc.convert(ServerMessages.ENTITY_DOES_NOT_EXIST)); - } - builder - .addResponsesBuilder() - .setUpdateResponse(UpdateResponse.newBuilder().setIdResponse(idResponse)); - break; - case DELETE_REQUEST: - final DeleteRequest deleteRequest = subRequest.getDeleteRequest(); - idResponse.setId(deleteRequest.getId()); - try { - grpcToCaosdb.getId(deleteRequest.getId()); - } catch (final NumberFormatException e) { - // ID wasn't an integer - idResponse.addErrors(caosdbToGrpc.convert(ServerMessages.ENTITY_DOES_NOT_EXIST)); - } - builder - .addResponsesBuilder() - .setDeleteResponse(DeleteResponse.newBuilder().setIdResponse(idResponse)); - break; - default: - throw new CaosDBException( - "Cannot process a " - + subRequest.getWrappedRequestsCase().name() - + " in a write request."); - } - } - return builder.build(); - } - private void addFileUpload( final WritableContainer container, final EntityInterface entity, diff --git a/src/main/java/org/caosdb/server/grpc/GRPCServer.java b/src/main/java/org/caosdb/server/grpc/GRPCServer.java index 73356140b85fb09af93bdca034a69685ce8cd4eb..494ce94706eff75415264b175f58cdaf16a2a442 100644 --- a/src/main/java/org/caosdb/server/grpc/GRPCServer.java +++ b/src/main/java/org/caosdb/server/grpc/GRPCServer.java @@ -81,8 +81,11 @@ public class GRPCServer { * @throws IOException */ private SslContext buildSslContext() - throws NoSuchAlgorithmException, UnrecoverableKeyException, KeyStoreException, - CertificateException, IOException { + throws NoSuchAlgorithmException, + UnrecoverableKeyException, + KeyStoreException, + CertificateException, + IOException { final KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); final char[] password = @@ -112,7 +115,9 @@ public class GRPCServer { return builder.build(); } - /** @return A list of services which should be added to the gRPC end-point. */ + /** + * @return A list of services which should be added to the gRPC end-point. + */ private List<ServerServiceDefinition> getEnabledServices() { final List<ServerServiceDefinition> services = new LinkedList<>(); @@ -153,8 +158,11 @@ public class GRPCServer { * @throws IOException */ private Server buildServer(final int port, final boolean tls) - throws UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException, - CertificateException, IOException { + throws UnrecoverableKeyException, + NoSuchAlgorithmException, + KeyStoreException, + CertificateException, + IOException { final NettyServerBuilder builder = NettyServerBuilder.forPort(port); if (tls) { @@ -182,8 +190,12 @@ public class GRPCServer { * @throws UnrecoverableKeyException */ public static void startServer() - throws IOException, InterruptedException, KeyStoreException, NoSuchAlgorithmException, - CertificateException, UnrecoverableKeyException { + throws IOException, + InterruptedException, + KeyStoreException, + NoSuchAlgorithmException, + CertificateException, + UnrecoverableKeyException { boolean started = false; final String port_https_str = getServerProperty(ServerProperties.KEY_GRPC_SERVER_PORT_HTTPS); diff --git a/src/main/java/org/caosdb/server/grpc/GrpcToCaosDBConverters.java b/src/main/java/org/caosdb/server/grpc/GrpcToCaosDBConverters.java index 0968c3eb8911161395d6944430fd6db90cfd504c..56d5c44095b45be7a947fb49a9b0859e7acf0670 100644 --- a/src/main/java/org/caosdb/server/grpc/GrpcToCaosDBConverters.java +++ b/src/main/java/org/caosdb/server/grpc/GrpcToCaosDBConverters.java @@ -1,8 +1,8 @@ /* * This file is a part of the CaosDB Project. * - * Copyright (C) 2021 IndiScale GmbH <info@indiscale.com> - * Copyright (C) 2021 Timm Fitschen <t.fitschen@indiscale.com> + * Copyright (C) 2023 IndiScale GmbH <info@indiscale.com> + * Copyright (C) 2023 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 @@ -16,9 +16,7 @@ * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. - * */ - package org.caosdb.server.grpc; import java.util.ArrayList; @@ -67,12 +65,13 @@ import org.caosdb.server.permissions.EntityACLFactory; import org.caosdb.server.utils.EntityStatus; import org.caosdb.server.utils.ServerMessages; +/** + * Utility class for converting GRPC's native objects into our own CaosDB objects. + * + * @author Timm Fitschen <t.fitschen@indiscale.com> + */ public class GrpcToCaosDBConverters { - public EntityID getId(final String id) { - return new EntityID(Integer.parseInt(id)); - } - public Role convert(final EntityRole role) { switch (role) { case ENTITY_ROLE_FILE: @@ -268,7 +267,7 @@ public class GrpcToCaosDBConverters { final Property result = new Property(new RetrieveEntity()); try { - result.setId(e.getId().isBlank() ? null : getId(e.getId())); + result.setId(e.getId().isBlank() ? null : new EntityID(e.getId())); } catch (final NumberFormatException exc) { result.addError(ServerMessages.ENTITY_DOES_NOT_EXIST); } @@ -326,7 +325,7 @@ public class GrpcToCaosDBConverters { new org.caosdb.server.entity.wrapper.Parent(new RetrieveEntity()); try { - result.setId(e.getId().isBlank() ? null : getId(e.getId())); + result.setId(e.getId().isBlank() ? null : new EntityID(e.getId())); } catch (final NumberFormatException exc) { result.addError(ServerMessages.ENTITY_DOES_NOT_EXIST); } @@ -348,7 +347,7 @@ public class GrpcToCaosDBConverters { private EntityInterface convert(EntityACL acl) { try { - EntityID id = getId(acl.getId()); + EntityID id = new EntityID(acl.getId()); UpdateEntity result = new UpdateEntity(id); result.setEntityACL(convertAcl(acl)); return result; diff --git a/src/main/java/org/caosdb/server/grpc/LoggingInterceptor.java b/src/main/java/org/caosdb/server/grpc/LoggingInterceptor.java index 9edd35090659e908ecf7f3fb502d34c36ac7e829..4e52485d4af4357b90a560baee6095c3b14683ae 100644 --- a/src/main/java/org/caosdb/server/grpc/LoggingInterceptor.java +++ b/src/main/java/org/caosdb/server/grpc/LoggingInterceptor.java @@ -1,5 +1,6 @@ package org.caosdb.server.grpc; +import io.grpc.Attributes.Key; import io.grpc.Context; import io.grpc.Contexts; import io.grpc.Metadata; @@ -7,18 +8,70 @@ import io.grpc.ServerCall; import io.grpc.ServerCall.Listener; import io.grpc.ServerCallHandler; import io.grpc.ServerInterceptor; +import java.net.SocketAddress; +import org.caosdb.server.CaosDBServer; +import org.caosdb.server.ServerProperties; +import org.restlet.routing.Template; +import org.restlet.util.Resolver; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +class CallResolver<ReqT, RespT> extends Resolver<String> { + + private ServerCall<ReqT, RespT> call; + private Metadata headers; + + public static final Metadata.Key<String> KEY_USER_AGENT = + Metadata.Key.of("user-agent", Metadata.ASCII_STRING_MARSHALLER); + public static final Key<SocketAddress> KEY_LOCAL_ADDRESS = io.grpc.Grpc.TRANSPORT_ATTR_LOCAL_ADDR; + public static final Key<SocketAddress> KEY_REMOTE_ADDRESS = + io.grpc.Grpc.TRANSPORT_ATTR_REMOTE_ADDR; + + public CallResolver( + ServerCall<ReqT, RespT> call, Metadata headers, ServerCallHandler<ReqT, RespT> next) { + this.call = call; + this.headers = headers; + } + + @Override + public String resolve(String name) { + switch (name) { + case "user-agent": + return headers.get(KEY_USER_AGENT); + case "remote-address": + return call.getAttributes().get(KEY_REMOTE_ADDRESS).toString(); + case "local-address": + return call.getAttributes().get(KEY_LOCAL_ADDRESS).toString(); + case "method": + return call.getMethodDescriptor().getFullMethodName(); + default: + break; + } + return null; + } +} + public class LoggingInterceptor implements ServerInterceptor { + private Template template; + + public LoggingInterceptor() { + String format = CaosDBServer.getServerProperty(ServerProperties.KEY_GRPC_RESPONSE_LOG_FORMAT); + if ("OFF".equalsIgnoreCase(format)) { + this.template = null; + } else if (format != null) { + this.template = new Template(format); + } + } + private static final Logger logger = LoggerFactory.getLogger(LoggingInterceptor.class.getName()); @Override public <ReqT, RespT> Listener<ReqT> interceptCall( ServerCall<ReqT, RespT> call, Metadata headers, ServerCallHandler<ReqT, RespT> next) { - - logger.info(call.getMethodDescriptor().getFullMethodName() + " - " + call.getAttributes()); + if (template != null) { + logger.info(template.format(new CallResolver<ReqT, RespT>(call, headers, next))); + } return Contexts.interceptCall(Context.current(), call, headers, next); } } diff --git a/src/main/java/org/caosdb/server/jobs/Job.java b/src/main/java/org/caosdb/server/jobs/Job.java index 9da74edd0db86f6d81a8636020afcdaf3faa9e09..7bb94635ed21c14c80c92d315539dd4fab5416ab 100644 --- a/src/main/java/org/caosdb/server/jobs/Job.java +++ b/src/main/java/org/caosdb/server/jobs/Job.java @@ -3,8 +3,8 @@ * * Copyright (C) 2018 Research Group Biomedical Physics, * Max-Planck-Institute for Dynamics and Self-Organization Göttingen - * Copyright (C) 2020-2021 IndiScale GmbH <info@indiscale.com> - * Copyright (C) 2020-2021 Timm Fitschen <t.fitschen@indiscale.com> + * Copyright (C) 2020-2023 IndiScale GmbH <info@indiscale.com> + * Copyright (C) 2020-2023 Timm Fitschen <t.fitschen@indiscale.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -22,10 +22,10 @@ package org.caosdb.server.jobs; import java.lang.reflect.InvocationTargetException; -import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedList; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Set; import org.apache.shiro.subject.Subject; @@ -33,9 +33,8 @@ import org.caosdb.server.CaosDBException; import org.caosdb.server.database.BackendTransaction; import org.caosdb.server.database.backend.transaction.GetIDByName; import org.caosdb.server.database.backend.transaction.IsSubType; -import org.caosdb.server.database.backend.transaction.RetrieveFullEntityTransaction; -import org.caosdb.server.database.backend.transaction.RetrieveParents; import org.caosdb.server.database.backend.transaction.RetrieveSparseEntity; +import org.caosdb.server.database.exceptions.EntityDoesNotExistException; import org.caosdb.server.database.exceptions.EntityWasNotUniqueException; import org.caosdb.server.database.exceptions.TransactionException; import org.caosdb.server.datatype.AbstractCollectionDatatype; @@ -44,13 +43,12 @@ import org.caosdb.server.datatype.ReferenceDatatype2; import org.caosdb.server.entity.EntityID; import org.caosdb.server.entity.EntityInterface; import org.caosdb.server.entity.Message; -import org.caosdb.server.entity.container.ParentContainer; +import org.caosdb.server.entity.Version; import org.caosdb.server.entity.container.TransactionContainer; import org.caosdb.server.entity.wrapper.Parent; import org.caosdb.server.jobs.core.JobFailureSeverity; import org.caosdb.server.transaction.Transaction; import org.caosdb.server.utils.EntityStatus; -import org.caosdb.server.utils.ServerMessages; import org.reflections.Reflections; /** @@ -73,7 +71,7 @@ public abstract class Job { public abstract JobTarget getTarget(); - protected <S, T> HashMap<S, T> getCache(final String name) { + protected <S, T> Map<S, T> getCache(final String name) { return getTransaction().getCache(name); } @@ -89,7 +87,7 @@ public abstract class Job { return getTransaction().getTransactor(); } - public <K extends BackendTransaction> K execute(final K t) { + protected <K extends BackendTransaction> K execute(final K t) { return getTransaction().execute(t, getTransaction().getAccess()); } @@ -185,8 +183,8 @@ public abstract class Job { if (targetParent.getId().equals(child.getId())) { return true; } - - } else if (targetParent.hasName()) { + } + if (targetParent.hasName()) { if (targetParent.getName().equals(child.getName())) { return true; } else { @@ -195,13 +193,7 @@ public abstract class Job { } // check direct parents of child - ParentContainer directParents; - if (child.hasParents()) { - directParents = child.getParents(); - } else { - directParents = resolve(child).getParents(); - } - for (final Parent directParent : directParents) { + for (final Parent directParent : child.getParents()) { EntityInterface resolvedDirectParent = null; if (directParent.hasId() && targetParent.hasId()) { if (directParent.getId().equals(targetParent.getId())) { @@ -231,78 +223,73 @@ public abstract class Job { } protected final boolean isValidSubType(final EntityID child, final EntityID parent) { - if (!"false".equals(getContainer().getFlags().get("cache"))) { - final HashMap<String, Boolean> isSubTypeCache = getCache("isSubType"); - final String key = child + "->" + parent; - final Boolean cached = isSubTypeCache.get(key); - if (cached == null) { - final Boolean toCache = isValidSubTypeNoCache(child, parent); - isSubTypeCache.put(key, toCache); - return toCache; - } else { - return cached; - } - } else { - return isValidSubTypeNoCache(child, parent); + Boolean result = null; + final Map<String, Boolean> isSubTypeCache = getCache("isSubType"); + + final String key = child + "->" + parent; + result = isSubTypeCache.get(key); + + if (result == null) { + result = isValidSubTypeNoCache(child, parent); + isSubTypeCache.put(key, result); } + + return result; } - protected final boolean isValidSubTypeNoCache(final EntityID child, final EntityID parent) { + private final boolean isValidSubTypeNoCache(final EntityID child, final EntityID parent) { return Objects.equals(child, parent) || execute(new IsSubType(child, parent)).isSubType(); } - protected final EntityInterface retrieveValidSparseEntityByName(final String name) - throws Message { - return retrieveValidSparseEntityById(retrieveValidIDByName(name), null); + private final EntityInterface retrieveValidLazyEntityByName(final String name, String version) { + EntityID id = null; + + final Map<String, EntityID> cache = getCache("validEntityByName"); + String key = name; + if (version != null && !version.equals("HEAD")) { + key += version; + } + + id = cache.get(key); + if (id == null) { + id = retrieveValidIDByName(name, version); + cache.put(key, id); + } + + return retrieveValidLazyEntityById(id, version); } - protected final EntityInterface retrieveValidSparseEntityById( - final EntityID id, final String version) throws Message { + private final EntityInterface retrieveValidLazyEntityById( + final EntityID id, final String version) { + EntityInterface result = null; - String resulting_version = version; - if (version == null || version.equals("HEAD")) { - // the targeted entity version is the entity after the transaction or the - // entity without a specific version. Thus we have to fetch the entity - // from the container if possible. - final EntityInterface ret = getEntityById(id); - if (ret != null) { - return ret; - } - } else if (version.startsWith("HEAD~")) { - final EntityInterface entById = getEntityById(id); - if (entById != null && entById.getEntityStatus() != EntityStatus.VALID) { - // if version is HEAD~{OFFSET} with {OFFSET} > 0 and the targeted entity is is to be - // updated, the actual offset has to be reduced by 1. HEAD always denotes the entity@HEAD - // *after* the successful transaction, so that it is consistent with subsequent retrieves. - final int offset = Integer.parseInt(version.substring(5)) - 1; - if (offset == 0) { - // special case HEAD~1 - resulting_version = "HEAD"; - } else { - resulting_version = new StringBuilder().append("HEAD~").append(offset).toString(); - } - } + final Map<String, EntityInterface> cache = getCache("validEntityById"); + String key = id.toString(); + if (version != null && !version.equals("HEAD")) { + key += version; } - final EntityInterface ret = - execute(new RetrieveSparseEntity(id, resulting_version)).getEntity(); - if (ret.getEntityStatus() == EntityStatus.NONEXISTENT) { - throw ServerMessages.ENTITY_DOES_NOT_EXIST; + result = cache.get(key); + if (result == null) { + result = retrieveValidLazyEntityByIdNoCache(id, version); + cache.put(key, result); } - return ret; - } - protected final EntityInterface retrieveValidEntity(final EntityID id) { - return execute(new RetrieveFullEntityTransaction(id)).getContainer().get(0); + return result; } - protected final EntityID retrieveValidIDByName(final String name) { - return execute(new GetIDByName(name)).getId(); + private final EntityInterface retrieveValidLazyEntityByIdNoCache( + final EntityID id, final String version) { + + final EntityInterface ret = execute(new RetrieveSparseEntity(id, version)).getEntity(); + if (ret.getEntityStatus() == EntityStatus.NONEXISTENT) { + throw new EntityDoesNotExistException(); + } + return new LazyEntityResolver(ret, getTransaction()); } - protected EntityInterface retrieveParentsOfValidEntity(final EntityInterface entity) { - execute(new RetrieveParents(entity)); - return entity; + protected final EntityID retrieveValidIDByName(final String name, String version) { + return execute(new GetIDByName(name, version)).getId(); } /** @@ -334,7 +321,7 @@ public abstract class Job { private static void scanJobClasspath() { if (allClasses == null || loadAlways == null) { allClasses = new HashMap<>(); - loadAlways = new ArrayList<>(); + loadAlways = new LinkedList<>(); Reflections jobPackage = new Reflections("org.caosdb.server.jobs.core"); Set<Class<? extends Job>> allClassesSet = jobPackage.getSubTypesOf(Job.class); allClassesSet.addAll(jobPackage.getSubTypesOf(FlagJob.class)); @@ -383,11 +370,11 @@ public abstract class Job { final EntityInterface entity, final Transaction<? extends TransactionContainer> transaction) { if (dt == null) { - return new ArrayList<Job>(); + return new LinkedList<Job>(); } if (dt instanceof ReferenceDatatype2) { return jobConfig.getConfiguredJobs( - EntityID.DEFAULT_DOMAIN, new EntityID(17), entity, transaction); + EntityID.DEFAULT_DOMAIN, new EntityID("17"), entity, transaction); } else if (dt instanceof AbstractCollectionDatatype) { final AbstractDatatype datatype = ((AbstractCollectionDatatype) dt).getDatatype(); return loadDataTypeSpecificJobs(datatype, entity, transaction); @@ -401,18 +388,22 @@ public abstract class Job { public List<Job> loadStandardJobs( final EntityInterface entity, final Transaction<? extends TransactionContainer> transaction) { - final ArrayList<Job> jobs = new ArrayList<>(); + final List<Job> jobs = new LinkedList<>(); // load permanent jobs - for (final Class<? extends Job> j : loadAlways) { - if (EntityJob.class.isAssignableFrom(j) - && j.getAnnotation(JobAnnotation.class).transaction().isInstance(transaction)) { - jobs.add(getJob(j, JobFailureSeverity.ERROR, entity, transaction)); + for (final Class<? extends Job> jobClass : loadAlways) { + if (EntityJob.class.isAssignableFrom(jobClass) + && jobClass.getAnnotation(JobAnnotation.class).transaction().isInstance(transaction)) { + Job j = getJob(jobClass, JobFailureSeverity.ERROR, entity, transaction); + if (j != null) { + jobs.add(j); + } } } // load general rules final List<Job> generalRules = - jobConfig.getConfiguredJobs(EntityID.DEFAULT_DOMAIN, new EntityID(0), entity, transaction); + jobConfig.getConfiguredJobs( + EntityID.DEFAULT_DOMAIN, new EntityID("0"), entity, transaction); if (generalRules != null) { jobs.addAll(generalRules); } @@ -541,10 +532,6 @@ public abstract class Job { + "]"; } - public void print() { - System.out.println(toString()); - } - public TransactionStage getTransactionStage() { return this.stage; } @@ -559,33 +546,80 @@ public abstract class Job { */ protected EntityInterface resolve(final EntityInterface entity) throws EntityWasNotUniqueException { - EntityInterface resolvedEntity = null; - if (!entity.hasId() && entity.hasName()) { - resolvedEntity = getEntityByName(entity.getName()); + return resolve(entity.getId(), entity.getName(), entity.getVersion()); + } + + protected EntityInterface resolve(EntityID id) { + return resolve(id, null, (String) null); + } + + protected EntityInterface resolve(EntityID id, String name, String versionId) + throws EntityDoesNotExistException, EntityWasNotUniqueException { + EntityInterface resolvedEntity = getEntityById(id); + + String resulting_version = versionId; + if (versionId == null || versionId.equals("HEAD")) { + resulting_version = null; + versionId = null; + } else if (versionId.startsWith("HEAD~")) { if (resolvedEntity == null) { - final EntityID eid = retrieveValidIDByName(entity.getName()); - entity.setId(eid); + resolvedEntity = getEntityByName(name); + } + if (resolvedEntity != null && resolvedEntity.getEntityStatus() == EntityStatus.QUALIFIED) { + // if versionId is HEAD~{OFFSET} with {OFFSET} > 0 and the targeted entity is is to be + // updated, the actual offset has to be reduced by 1. HEAD always denotes the entity@HEAD + // *after* the successful transaction, so that it is consistent with subsequent retrieves. + final int offset = Integer.parseInt(versionId.substring(5)) - 1; + if (offset == 0) { + // special case HEAD~1 + resulting_version = "HEAD"; + } else { + resulting_version = new StringBuilder().append("HEAD~").append(offset).toString(); + } } + resolvedEntity = null; + } else { + // We want another version, throw away current result. + resolvedEntity = null; } - if (entity.hasId()) { - // get entity from container - resolvedEntity = getEntityById(entity.getId()); - if (resolvedEntity == null && !entity.getId().isTemporary()) { - resolvedEntity = retrieveValidEntity(entity.getId()); + if (resolvedEntity == null && id != null) { + try { + resolvedEntity = retrieveValidLazyEntityById(id, resulting_version); + } catch (EntityDoesNotExistException e) { + // ignore here, try to find by name. } } + // Last resort: find by name ... + if (resolvedEntity == null && versionId == null) { + // ... from current transaction's container + resolvedEntity = getEntityByName(name); + } + + if (resolvedEntity == null && name != null) { + // ... from database + resolvedEntity = retrieveValidLazyEntityByName(name, resulting_version); + } + + if (resolvedEntity == null) { + throw new EntityDoesNotExistException(); + } return resolvedEntity; } + protected EntityInterface resolve(EntityID id, String name, Version version) { + String versionId = version != null ? version.getId() : null; + return resolve(id, name, versionId); + } + /** * Return those matching jobs which are annotated with the "loadAlways" attribute. * * @return A list with the jobs. */ public static List<Job> loadPermanentContainerJobs(final Transaction<?> transaction) { - final ArrayList<Job> jobs = new ArrayList<>(); + final List<Job> jobs = new LinkedList<>(); // load permanent jobs: ContainerJob classes with the correct transaction for (final Class<? extends Job> j : loadAlways) { if (ContainerJob.class.isAssignableFrom(j) @@ -595,4 +629,22 @@ public abstract class Job { } return jobs; } + + @Override + public boolean equals(Object obj) { + if (obj != null && obj instanceof Job) { + Job other = (Job) obj; + return other.getClass().equals(this.getClass()) + && Objects.equals(other.getEntity(), this.getEntity()) + && other.getFailureSeverity().equals(this.getFailureSeverity()); + } + return false; + } + + @Override + public int hashCode() { + return getClass().hashCode() + + (getEntity() == null ? 0 : getEntity().hashCode()) + + getFailureSeverity().hashCode(); + } } diff --git a/src/main/java/org/caosdb/server/jobs/JobConfig.java b/src/main/java/org/caosdb/server/jobs/JobConfig.java index 40917b36714ee95ef491a266580068a06f51703a..83300e00d4be7c463f4b92f4fdbb521c17739a7b 100644 --- a/src/main/java/org/caosdb/server/jobs/JobConfig.java +++ b/src/main/java/org/caosdb/server/jobs/JobConfig.java @@ -1,8 +1,8 @@ /* * This file is a part of the CaosDB Project. * - * Copyright (C) 2021 IndiScale GmbH <info@indiscale.com> - * Copyright (C) 2021 Timm Fitschen <t.fitschen@indiscale.com> + * Copyright (C) 2021,2023 IndiScale GmbH <info@indiscale.com> + * Copyright (C) 2021,2023 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 @@ -36,7 +36,7 @@ import org.caosdb.server.entity.EntityID; import org.caosdb.server.entity.EntityInterface; import org.caosdb.server.entity.RetrieveEntity; import org.caosdb.server.entity.container.TransactionContainer; -import org.caosdb.server.entity.wrapper.Property; +import org.caosdb.server.entity.wrapper.EntityWrapper; import org.caosdb.server.jobs.core.JobFailureSeverity; import org.caosdb.server.transaction.Transaction; import org.caosdb.server.utils.ConfigurationException; @@ -141,8 +141,8 @@ public class JobConfig { "Could not parse the job rules. Lines of five comma-separated values expected"); } try { - final Integer domain = Integer.parseInt(row[0]); - final Integer entity = Integer.parseInt(row[1]); + final String domain = row[0]; + final String entity = row[1]; final String transaction = row[2]; final String job = row[3]; final JobFailureSeverity severity = JobFailureSeverity.valueOf(row[4]); @@ -158,7 +158,7 @@ public class JobConfig { } /** - * Factory method for the instanciation and configuration of jobs for a specific entity. + * Factory method for the instantiation and configuration of jobs for a specific entity. * * @param domain the domain of the rule * @param entity the entity of the rule (this might be a particular datatype or a role, like @@ -173,8 +173,8 @@ public class JobConfig { final EntityInterface target, final Transaction<? extends TransactionContainer> transaction) { String transactionType; - if (target instanceof Property) { - transactionType = getTransactionType(((Property) target).getDomainEntity()); + if (target instanceof EntityWrapper) { + transactionType = getTransactionType(((EntityWrapper) target).getWrapped()); } else { transactionType = getTransactionType(target); } @@ -184,9 +184,13 @@ public class JobConfig { final List<Job> result = new LinkedList<>(); jobRules.forEach( config -> { - result.add( - Job.getJob( - (String) config[0], (JobFailureSeverity) config[1], target, transaction)); + Job j = + Job.getJob((String) config[0], (JobFailureSeverity) config[1], target, transaction); + if (j == null) { + throw new NullPointerException("Could not load job: " + config[0]); + } else { + result.add(j); + } }); return result; } diff --git a/src/main/java/org/caosdb/server/jobs/LazyEntityResolver.java b/src/main/java/org/caosdb/server/jobs/LazyEntityResolver.java new file mode 100644 index 0000000000000000000000000000000000000000..7088ce7d35903daf34a0e060241aa22d749988c0 --- /dev/null +++ b/src/main/java/org/caosdb/server/jobs/LazyEntityResolver.java @@ -0,0 +1,101 @@ +/* + * This file is a part of the CaosDB Project. + * + * Copyright (C) 2023 IndiScale GmbH <info@indiscale.com> + * Copyright (C) 2023 Timm Fitschen <t.fitschen@indiscale.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ +package org.caosdb.server.jobs; + +import org.caosdb.server.database.backend.transaction.RetrieveParents; +import org.caosdb.server.database.backend.transaction.RetrieveProperties; +import org.caosdb.server.entity.EntityInterface; +import org.caosdb.server.entity.container.ParentContainer; +import org.caosdb.server.entity.container.PropertyContainer; +import org.caosdb.server.entity.wrapper.EntityWrapper; +import org.caosdb.server.transaction.Transaction; + +/** + * Implementation of {@link EntityInterface} which retrieves the parents and properties only when + * requested via getProperties and getParents. + * + * <p>This is particularly useful for jobs sharing the same entity object between jobs which need + * different parts of it while reducing the logic of checking whether the entity is "only" sparse, + * or has been retrieved fully and so on. + * + * <p>However, when the entities parents or properties are being retrieved after the transaction's + * access has been released, an {@link IllegalStateException} will be thrown. + * + * @author Timm Fitschen <t.fitschen@indiscale.com> + */ +public class LazyEntityResolver extends EntityWrapper { + + private Transaction<?> transaction; + private boolean propertiesResolved = false; + private boolean parentsResolved = false; + + /** + * Constructor. + * + * <p>Initialize this with a sparse entity. + */ + public LazyEntityResolver(EntityInterface entity, Transaction<?> transaction) { + super(entity); + this.transaction = transaction; + } + + public void resolveParents() { + if (parentsResolved) return; + parentsResolved = true; + transaction.execute(new RetrieveParents(getWrapped()), transaction.getAccess()); + } + + private void resolveProperties() { + if (propertiesResolved) return; + propertiesResolved = true; + transaction.execute(new RetrieveProperties(getWrapped()), transaction.getAccess()); + } + + public void resolveAll() { + resolveParents(); + resolveProperties(); + // release the transaction for the garbage collector + this.transaction = null; + } + + @Override + public ParentContainer getParents() { + resolveParents(); + return super.getParents(); + } + + @Override + public boolean hasParents() { + resolveParents(); + return super.hasParents(); + } + + @Override + public PropertyContainer getProperties() { + resolveProperties(); + return super.getProperties(); + } + + @Override + public boolean hasProperties() { + resolveProperties(); + return super.hasProperties(); + } +} diff --git a/src/main/java/org/caosdb/server/jobs/Schedule.java b/src/main/java/org/caosdb/server/jobs/Schedule.java index 57474da515418745ed0457962596dfce233bff48..1b7ac8f4b41118803e7d5a540be6a1a3ba57cfec 100644 --- a/src/main/java/org/caosdb/server/jobs/Schedule.java +++ b/src/main/java/org/caosdb/server/jobs/Schedule.java @@ -3,8 +3,8 @@ * * Copyright (C) 2018 Research Group Biomedical Physics, * Max-Planck-Institute for Dynamics and Self-Organization Göttingen - * Copyright (C) 2020-2021 IndiScale GmbH <info@indiscale.com> - * Copyright (C) 2020-2021 Timm Fitschen <t.fitschen@indiscale.com> + * Copyright (C) 2020-2021,2023 IndiScale GmbH <info@indiscale.com> + * Copyright (C) 2020-2021,2023 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 @@ -35,6 +35,8 @@ import org.caosdb.server.entity.EntityInterface; * <p>The Schedule class orders jobs by {@link TransactionStage} and also assures that jobs are * skipped when appropriate and to prevent that jobs run more than once (because sometimes they * trigger each other). + * + * @author Timm Fitschen <t.fitschen@indiscale.com> */ public class Schedule { @@ -44,20 +46,26 @@ public class Schedule { public List<ScheduledJob> addAll(final Collection<Job> jobs) { final List<ScheduledJob> result = new ArrayList<ScheduledJob>(jobs.size()); for (final Job j : jobs) { - result.add(add(j)); + ScheduledJob scheduledJob = add(j); + if (scheduledJob != null) { + result.add(scheduledJob); + } } return result; } public ScheduledJob add(final Job j) { - final ScheduledJob ret = new ScheduledJob(j); - List<ScheduledJob> jobs = jobLists.get(ret.getTransactionStage().ordinal()); + final ScheduledJob scheduled = new ScheduledJob(j); + List<ScheduledJob> jobs = jobLists.get(scheduled.getTransactionStage().ordinal()); if (jobs == null) { jobs = new CopyOnWriteArrayList<ScheduledJob>(); - jobLists.put(ret.getTransactionStage().ordinal(), jobs); + jobLists.put(scheduled.getTransactionStage().ordinal(), jobs); + } + if (!jobs.contains(scheduled)) { + jobs.add(scheduled); + return scheduled; } - jobs.add(ret); - return ret; + return null; } /** Run all Jobs from the specified {@link TransactionStage}. */ @@ -71,7 +79,7 @@ public class Schedule { } public void runJob(final ScheduledJob scheduledJob) { - if (scheduledJob.skip()) { + if (scheduledJob == null || scheduledJob.skip()) { return; } final ScheduledJob parent = this.running; diff --git a/src/main/java/org/caosdb/server/jobs/ScheduledJob.java b/src/main/java/org/caosdb/server/jobs/ScheduledJob.java index 2122b34de5f0b60db4c2b6d4c1c2358d3df02851..1754f19f5fdab5b32e19fcba7758481f0dba8948 100644 --- a/src/main/java/org/caosdb/server/jobs/ScheduledJob.java +++ b/src/main/java/org/caosdb/server/jobs/ScheduledJob.java @@ -1,8 +1,8 @@ /* * 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> + * Copyright (C) 2020,2023 IndiScale GmbH <info@indiscale.com> + * Copyright (C) 2020,2023 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 @@ -28,7 +28,7 @@ import org.caosdb.server.entity.Message; * <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) + * @author Timm Fitschen <t.fitschen@indiscale.com> */ public class ScheduledJob { @@ -102,4 +102,18 @@ public class ScheduledJob { public Job getJob() { return job; } + + @Override + public boolean equals(Object obj) { + if (obj instanceof ScheduledJob) { + ScheduledJob other = (ScheduledJob) obj; + return this.job.equals(other.job); + } + return false; + } + + @Override + public int hashCode() { + return job.hashCode(); + } } diff --git a/src/main/java/org/caosdb/server/jobs/core/AutoCreateDirs.java b/src/main/java/org/caosdb/server/jobs/core/AutoCreateDirs.java index a2644c63e7b75dbc7e4ae442ed07d987a52d4fb1..d7a632bcfdbb7875a382617f3ee9591d6cbd4020 100644 --- a/src/main/java/org/caosdb/server/jobs/core/AutoCreateDirs.java +++ b/src/main/java/org/caosdb/server/jobs/core/AutoCreateDirs.java @@ -14,6 +14,7 @@ import org.caosdb.server.jobs.EntityFlagJob; import org.caosdb.server.jobs.JobAnnotation; import org.caosdb.server.jobs.TransactionStage; import org.caosdb.server.permissions.EntityACL; +import org.caosdb.server.transaction.WriteTransactionInterface; @JobAnnotation( flag = "autoCreateDirs", @@ -78,6 +79,7 @@ public class AutoCreateDirs extends EntityFlagJob { child.setParentDirectory(newFD.getEntityId()); final EntityInterface newDir = new InsertEntity(name, Role.Directory); + newDir.setId(((WriteTransactionInterface) getTransaction()).generateId()); newDir.setEntityACL(EntityACL.getOwnerACLFor(getUser())); newDir.setFSODescriptor(newFD); getContainer().add(newDir); diff --git a/src/main/java/org/caosdb/server/jobs/core/CheckChildDependencyExistent.java b/src/main/java/org/caosdb/server/jobs/core/CheckChildDependencyExistent.java deleted file mode 100644 index d60db5a3ea2f58caefe240894920318e01efc8a6..0000000000000000000000000000000000000000 --- a/src/main/java/org/caosdb/server/jobs/core/CheckChildDependencyExistent.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * ** header v3.0 - * This file is a part of the CaosDB Project. - * - * Copyright (C) 2018 Research Group Biomedical Physics, - * Max-Planck-Institute for Dynamics and Self-Organization Göttingen - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <https://www.gnu.org/licenses/>. - * - * ** end header - */ -package org.caosdb.server.jobs.core; - -import java.util.List; -import java.util.Objects; -import org.caosdb.server.database.backend.transaction.GetChildren; -import org.caosdb.server.entity.EntityID; -import org.caosdb.server.entity.EntityInterface; -import org.caosdb.server.jobs.EntityJob; -import org.caosdb.server.utils.EntityStatus; -import org.caosdb.server.utils.ServerMessages; - -/** - * Check whether any children of this entity do exist. There must not be any children left when an - * entity is to be deleted. If all children are to be deleted, too, the test passes. - * - * @author tf - */ -public class CheckChildDependencyExistent extends EntityJob { - - @Override - public final void run() { - if (getEntity().getDomain() == null - || Objects.equals(getEntity().getDomain(), EntityID.DEFAULT_DOMAIN)) { - - final List<EntityID> children = execute(new GetChildren(getEntity().getId())).getList(); - - // loop: - for (final EntityID id : children) { - final EntityInterface foreign = getEntityById(id); - if (foreign == null) { - // if the child is not in the container, the test fails. - getEntity().addError(ServerMessages.REQUIRED_BY_PERSISTENT_ENTITY); - getEntity().addInfo("Required by entity " + id + "."); - getEntity().setEntityStatus(EntityStatus.UNQUALIFIED); - } - - // // loop through all entities in the current container - // for (final EntityInterface e : getContainer()) { - // - // // if e is a child - // if (e.getId().equals(id)) { - // if (e.getEntityStatus() == EntityStatus.UNQUALIFIED) { - // getEntity().addError(ServerMessages.REQUIRED_BY_UNQUALIFIED); - // getEntity().addInfo("Required by entity " + id + "."); - // getEntity().setEntityStatus(EntityStatus.UNQUALIFIED); - // } else { - // // if the children are among the entities in the - // // container which is to be deleted, the test passes - // e.acceptObserver(new Observer() { - // @Override - // public boolean notifyObserver(final String evt, - // final Observable o) { - // if (evt == Entity.ENTITY_STATUS_CHANGED_EVENT && o == e) { - // if (e.getEntityStatus() == EntityStatus.UNQUALIFIED) { - // getEntity().addError( - // ServerMessages.REQUIRED_BY_UNQUALIFIED); - // getEntity().addInfo("Required by entity " + id + "."); - // getEntity().setEntityStatus(EntityStatus.UNQUALIFIED); - // return false; - // } - // } - // return true; - // } - // }); - // } - // continue loop; - // } - // } - - // // if the child is not in the container, the test fails. - // getEntity().addError(ServerMessages.REQUIRED_BY_PERSISTENT_ENTITY); - // getEntity().addInfo("Required by entity " + id + "."); - // getEntity().setEntityStatus(EntityStatus.UNQUALIFIED); - } - } - } -} 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 40e01d2636509774555f1514c75bb0bf16870539..4a43d4a2985ad3607bb5cb9b2789acd085cc31e0 100644 --- a/src/main/java/org/caosdb/server/jobs/core/CheckDatatypePresent.java +++ b/src/main/java/org/caosdb/server/jobs/core/CheckDatatypePresent.java @@ -1,9 +1,10 @@ /* - * ** header v3.0 * This file is a part of the CaosDB Project. * * Copyright (C) 2018 Research Group Biomedical Physics, * Max-Planck-Institute for Dynamics and Self-Organization Göttingen + * Copyright (C) 2023 IndiScale GmbH <info@indiscale.com> + * Copyright (C) 2023 Timm Fitschen <t.fitschen@indiscale.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -17,8 +18,6 @@ * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. - * - * ** end header */ package org.caosdb.server.jobs.core; @@ -29,6 +28,7 @@ import org.caosdb.server.database.exceptions.EntityWasNotUniqueException; import org.caosdb.server.datatype.AbstractCollectionDatatype; import org.caosdb.server.datatype.AbstractDatatype; import org.caosdb.server.datatype.ReferenceDatatype2; +import org.caosdb.server.entity.EntityID; import org.caosdb.server.entity.EntityInterface; import org.caosdb.server.entity.InsertEntity; import org.caosdb.server.entity.Message; @@ -41,10 +41,11 @@ import org.caosdb.server.utils.EntityStatus; import org.caosdb.server.utils.ServerMessages; /** - * Check whether the entity has a data type. Assign the data type of the abstract property if - * necessary + * Check whether the entity has a data type. + * + * <p>Assign the data type of the abstract property if necessary. * - * @author tf + * @author Timm Fitschen <t.fitschen@indiscale.com> */ public final class CheckDatatypePresent extends EntityJob { @@ -115,44 +116,19 @@ public final class CheckDatatypePresent extends EntityJob { } private void checkReference2(final ReferenceDatatype2 datatype) throws Message { + EntityID datatypeId = datatype.getId(); + if (datatypeId == null) { + datatypeId = new EntityID(datatype.getName()); + } + EntityInterface datatypeEntity = resolve(datatypeId, datatype.getName(), (String) null); - if (datatype.getId() == null) { - // try and get from container... - final EntityInterface datatypeEntity = getEntityByName(datatype.getName()); - - // if the container carried a corresponding entity - if (datatypeEntity != null) { - assertAllowedToUse(datatypeEntity); - - // ... we set it as the datatypevalue - datatype.setEntity(datatypeEntity); - } else { - - // else try and get from database. - final EntityInterface validDatatypeEntity = - retrieveValidSparseEntityByName(datatype.getName()); - assertAllowedToUse(validDatatypeEntity); - datatype.setId(validDatatypeEntity.getId()); - } - } else if (datatype.getId().isTemporary()) { - final EntityInterface datatypeEntity = getEntityById(datatype.getId()); - - // if the container carried a corresponding entity - if (datatypeEntity != null) { - assertAllowedToUse(datatypeEntity); - // ... we set it as the datatype - datatype.setEntity(datatypeEntity); - } else { - - throw ServerMessages.UNKNOWN_DATATYPE; - } - } else { - - final EntityInterface validDatatypeEntity = - retrieveValidSparseEntityById(datatype.getId(), null); - assertAllowedToUse(validDatatypeEntity); - datatype.setEntity(validDatatypeEntity); + if (datatypeEntity == null) { + throw ServerMessages.UNKNOWN_DATATYPE; } + + // do the actual checking + assertAllowedToUse(datatypeEntity); + datatype.setEntity(datatypeEntity); } private void assertAllowedToUse(final EntityInterface datatype) { @@ -160,62 +136,39 @@ public final class CheckDatatypePresent extends EntityJob { } private void checkIfOverride() throws Message { - if (getEntity().hasId() && !getEntity().getId().isTemporary()) { - // get data type from database - final EntityInterface foreign = retrieveValidSparseEntityById(getEntity().getId(), null); - - if (foreign.hasDatatype() && !foreign.getDatatype().equals(getEntity().getDatatype())) { - // is override! - getEntity().setDatatypeOverride(true); - } - } else { - // get data type from container - EntityInterface abstractProperty = null; - if (getEntity().hasId()) { - abstractProperty = getEntityById(getEntity().getId()); - } else if (getEntity().hasName()) { - abstractProperty = getEntityByName(getEntity().getName()); - } - if (abstractProperty != null && abstractProperty.hasDatatype()) { - if (!getEntity().getDatatype().equals(abstractProperty.getDatatype())) { - // is override! - getEntity().setDatatypeOverride(true); - } - } + EntityInterface abstractProperty = resolve(getEntity()); + if (abstractProperty != null + && abstractProperty.hasDatatype() + && !abstractProperty.getDatatype().equals(getEntity().getDatatype())) { + // is override! + getEntity().setDatatypeOverride(true); } } + /** + * If this is a record type property or a concrete property, assign the data type of the + * corresponding abstract property. + */ private void inheritDatatypeFromAbstractEntity() throws Message { - // if this is a record type property or a concrete property, assign - // the data type of the corresponding abstract property. - if (getEntity().hasId() && !getEntity().getId().isTemporary()) { - // get from data base - final EntityInterface foreign = retrieveValidSparseEntityById(getEntity().getId(), null); - inheritDatatypeFromForeignEntity(foreign); - } else if (getEntity().hasId() && getEntity().getId().isTemporary()) { - // get from container - final EntityInterface foreign = getEntityById(getEntity().getId()); - inheritDatatypeFromForeignEntity(foreign); - } - } - - private void inheritDatatypeFromForeignEntity(final EntityInterface foreign) { - if (foreign != null && foreign.hasDatatype()) { - getEntity().setDatatype(foreign.getDatatype()); - } else if (foreign != null && foreign != getEntity() && foreign.getRole() == Role.RecordType) { - getEntity().setDatatype(ReferenceDatatype2.datatypeFactory(foreign.getId())); + EntityInterface abstractProperty = resolve(getEntity()); + if (abstractProperty != null && abstractProperty.hasDatatype()) { + getEntity().setDatatype(abstractProperty.getDatatype()); + } else if (abstractProperty != null + && abstractProperty != getEntity() + && abstractProperty.getRole() == Role.RecordType) { + getEntity().setDatatype(ReferenceDatatype2.datatypeFactory(abstractProperty.getId())); } } private void resolveId(final EntityInterface entity) { if (!entity.hasId() && entity.hasName()) { try { - entity.setId(retrieveValidIDByName(entity.getName())); - if (entity.getEntityStatus() != EntityStatus.UNQUALIFIED) { - entity.setEntityStatus(EntityStatus.VALID); + EntityInterface resolved = resolve(entity); + if (resolved == null) { + entity.addError(ServerMessages.ENTITY_DOES_NOT_EXIST); + } else { + entity.setId(resolved.getId()); } - } catch (final EntityDoesNotExistException exc) { - entity.addError(ServerMessages.ENTITY_DOES_NOT_EXIST); } catch (final EntityWasNotUniqueException exc) { entity.addError(ServerMessages.ENTITY_NAME_DUPLICATES); } @@ -227,14 +180,12 @@ public final class CheckDatatypePresent extends EntityJob { AbstractDatatype datatype = null; for (final EntityInterface parent : getEntity().getParents()) { - EntityInterface parentEntity = null; - if (!parent.getId().isTemporary()) { - parentEntity = retrieveValidSparseEntityById(parent.getId(), null); - } else { - parentEntity = getEntityById(parent.getId()); + EntityInterface parentEntity = resolve(parent); + if (!parentEntity.hasDatatype() && parentEntity.getEntityStatus() == EntityStatus.QUALIFIED) { runJobFromSchedule(parentEntity, CheckDatatypePresent.class); } - if (parentEntity.hasDatatype()) { + if (parentEntity.hasDatatype() + && parentEntity.getEntityStatus() != EntityStatus.UNQUALIFIED) { if (datatype != null && !parentEntity.getDatatype().equals(datatype)) { getEntity().addError(ServerMessages.DATATYPE_INHERITANCE_AMBIGUOUS); return; diff --git a/src/main/java/org/caosdb/server/jobs/core/CheckReferenceDependencyExistent.java b/src/main/java/org/caosdb/server/jobs/core/CheckDependenciesBeforeDeletion.java similarity index 53% rename from src/main/java/org/caosdb/server/jobs/core/CheckReferenceDependencyExistent.java rename to src/main/java/org/caosdb/server/jobs/core/CheckDependenciesBeforeDeletion.java index 2edbc5a270db93312c73b8c960c615a161e8907b..ecbbf4af7fb4a81605d61a3d251b88c4f1d0917a 100644 --- a/src/main/java/org/caosdb/server/jobs/core/CheckReferenceDependencyExistent.java +++ b/src/main/java/org/caosdb/server/jobs/core/CheckDependenciesBeforeDeletion.java @@ -1,9 +1,10 @@ /* - * ** header v3.0 * This file is a part of the CaosDB Project. * * Copyright (C) 2018 Research Group Biomedical Physics, * Max-Planck-Institute for Dynamics and Self-Organization Göttingen + * Copyright (C) 2023 IndiScale GmbH <info@indiscale.com> + * Copyright (C) 2023 Timm Fitschen <t.fitschen@indiscale.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -17,8 +18,6 @@ * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. - * - * ** end header */ package org.caosdb.server.jobs.core; @@ -32,13 +31,13 @@ import org.caosdb.server.utils.EntityStatus; import org.caosdb.server.utils.ServerMessages; /** - * Check whether an entity is referenced by other entities. In the event that someone wants to - * delete this entity, this entity must not be referenced by any other entity which is not deleted - * along with this entity. + * Check whether an entity is referenced by other entities, is being used as a data type by other + * entities or has direct children which are not to be deleted along with this entity and add an + * appropriate error if so. * - * @author tf + * @author Timm Fitschen <t.fitschen@indiscale.com> */ -public class CheckReferenceDependencyExistent extends EntityJob { +public class CheckDependenciesBeforeDeletion extends EntityJob { @Override public final void run() { if (getEntity().getDomain() == null @@ -59,42 +58,6 @@ public class CheckReferenceDependencyExistent extends EntityJob { getEntity().addInfo("Required by entity " + id + "."); getEntity().setEntityStatus(EntityStatus.UNQUALIFIED); } - // for (final EntityInterface e : getContainer()) { - // if (e.getId().equals(id)) { - // // entity is in the container. - // if (e.getEntityStatus() == EntityStatus.UNQUALIFIED) { - // getEntity().addError(ServerMessages.REQUIRED_BY_UNQUALIFIED); - // getEntity().addInfo("Required by entity " + id + "."); - // getEntity().setEntityStatus(EntityStatus.UNQUALIFIED); - // } else { - // e.acceptObserver(new Observer() { - // @Override - // public boolean notifyObserver(final String evt, - // final Observable o) { - // if (e == o && evt == Entity.ENTITY_STATUS_CHANGED_EVENT) { - // if (e.getEntityStatus() == EntityStatus.UNQUALIFIED) { - // getEntity().addError( - // ServerMessages.REQUIRED_BY_UNQUALIFIED); - // getEntity().addInfo("Required by entity " + id + "."); - // getEntity().setEntityStatus(EntityStatus.UNQUALIFIED); - // return false; - // } - // } - // return true; - // } - // }); - // } - // - // continue loop; - // } - // } - - // // dependent entity is not in the container. That is, it will - // // not be deleted. Therefore, this entity cannot be deleted - // // either. - // getEntity().addError(ServerMessages.REQUIRED_BY_PERSISTENT_ENTITY); - // getEntity().addInfo("Required by entity " + id + "."); - // getEntity().setEntityStatus(EntityStatus.UNQUALIFIED); } } } diff --git a/src/main/java/org/caosdb/server/jobs/core/CheckParOblPropPresent.java b/src/main/java/org/caosdb/server/jobs/core/CheckParOblPropPresent.java index e6ba9da430e6218e5c40fba968c309ed94152bf6..eba79831468b1f6799507ca0866a64e594997caa 100644 --- a/src/main/java/org/caosdb/server/jobs/core/CheckParOblPropPresent.java +++ b/src/main/java/org/caosdb/server/jobs/core/CheckParOblPropPresent.java @@ -93,7 +93,7 @@ public class CheckParOblPropPresent extends EntityJob { // loop over all properties of the entity for (final EntityInterface entityProperty : getEntity().getProperties()) { - if (isSubType(entityProperty, parentProperty)) { + if (isSubType(resolve(entityProperty), parentProperty)) { // continue outer loop means that we // found an entityProperty that // implements the obligatory 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 8895ae9adb4d12da2f7789a8798ca0fb9d183c05..fd235c21319d7b573db1cd42b23d9184aa7e56e6 100644 --- a/src/main/java/org/caosdb/server/jobs/core/CheckParValid.java +++ b/src/main/java/org/caosdb/server/jobs/core/CheckParValid.java @@ -1,9 +1,10 @@ /* - * ** header v3.0 * This file is a part of the CaosDB Project. * * Copyright (C) 2018 Research Group Biomedical Physics, * Max-Planck-Institute for Dynamics and Self-Organization Göttingen + * Copyright (C) 2023 IndiScale GmbH <info@indiscale.com> + * Copyright (C) 2023 Timm Fitschen <t.fitschen@indiscale.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -17,8 +18,6 @@ * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. - * - * ** end header */ package org.caosdb.server.jobs.core; @@ -42,7 +41,7 @@ import org.caosdb.server.utils.ServerMessages; /** * Check whether all parents of an entity are valid/qualified. * - * @author tf + * @author Timm Fitschen <t.fitschen@indiscale.com> */ @JobAnnotation(stage = TransactionStage.PRE_CHECK) public class CheckParValid extends EntityJob { @@ -60,73 +59,21 @@ public class CheckParValid extends EntityJob { if (!parent.hasId() && !parent.hasName()) { // The parent has neither an id nor a name. // Therefore it cannot be identified. - - throw ServerMessages.ENTITY_HAS_NO_NAME_OR_ID; - } - - if (parent.hasId()) { - // check parent by id - if (!parent.getId().isTemporary()) { - // id isn't temporary, i.e., parent is already in the database and - // can be retrieved by id - final EntityInterface foreign = retrieveValidSparseEntityById(parent.getId(), null); - // check permissions for this - // parentforeign.acceptObserver(o) - assertAllowedToUse(foreign); - parent.setAffiliation(getAffiliation(getEntity().getRole(), foreign.getRole())); - continue; - } else { - // id < 0 (parent is to be stored along with - // this entity) - - // get entity with corresponding (negative) id - // from container - final EntityInterface foreign = getEntityById(parent.getId()); - - // if the container carried a corresponding - // entity - if (foreign != null) { - assertAllowedToUse(foreign); - - parent.setAffiliation(getAffiliation(getEntity().getRole(), foreign.getRole())); - - // ... we can set it as the parent - parent.linkIdToEntity(foreign); - continue; - } - } + parent.addError(ServerMessages.ENTITY_HAS_NO_NAME_OR_ID); } - - // parent doesn't have an id. - if (parent.hasName()) { - if (getEntityByName(parent.getName()) != null) { - // get the parent entity from the container by its - // name - final EntityInterface foreign = getEntityByName(parent.getName()); - - assertAllowedToUse(foreign); - parent.setAffiliation(getAffiliation(getEntity().getRole(), foreign.getRole())); - parent.linkIdToEntity(foreign); - continue; - } else { - // check parent by name (parent is expected to be - // valid). This only works if the name is unique. - final EntityInterface foreign = retrieveValidSparseEntityByName(parent.getName()); - assertAllowedToUse(foreign); - parent.setAffiliation(getAffiliation(getEntity().getRole(), foreign.getRole())); - parent.setId(foreign.getId()); - continue; - } + try { + EntityInterface foreign = resolve(parent.getId(), parent.getName(), (String) null); + assertAllowedToUse(foreign); + parent.linkIdToEntity(foreign); + parent.setAffiliation(getAffiliation(getEntity().getRole(), foreign.getRole())); + } catch (EntityDoesNotExistException e) { + parent.addError(ServerMessages.ENTITY_DOES_NOT_EXIST); } - - parent.addError(ServerMessages.ENTITY_DOES_NOT_EXIST); } catch (final Message m) { parent.addError(m); } catch (AuthorizationException e) { parent.addError(ServerMessages.AUTHORIZATION_ERROR); parent.addInfo(e.getMessage()); - } catch (final EntityDoesNotExistException exc) { - parent.addError(ServerMessages.ENTITY_DOES_NOT_EXIST); } catch (final EntityWasNotUniqueException exc) { parent.addError(ServerMessages.ENTITY_NAME_DUPLICATES); } 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 23a92340f5befbfbe7ded2fe95d3244b5ff78594..bfd2bc3019d1d4ae6204ebbe5b5975932603ed76 100644 --- a/src/main/java/org/caosdb/server/jobs/core/CheckPropValid.java +++ b/src/main/java/org/caosdb/server/jobs/core/CheckPropValid.java @@ -1,9 +1,10 @@ /* - * ** header v3.0 * This file is a part of the CaosDB Project. * * Copyright (C) 2018 Research Group Biomedical Physics, * Max-Planck-Institute for Dynamics and Self-Organization Göttingen + * Copyright (C) 2023 IndiScale GmbH <info@indiscale.com> + * Copyright (C) 2023 Timm Fitschen <t.fitschen@indiscale.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -17,8 +18,6 @@ * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. - * - * ** end header */ package org.caosdb.server.jobs.core; @@ -41,7 +40,7 @@ import org.caosdb.server.utils.ServerMessages; /** * Check whether all properties of an entity are valid or qualified. * - * @author tf + * @author Timm Fitschen <t.fitschen@indiscale.com> */ @JobAnnotation(stage = TransactionStage.PRE_CHECK) public class CheckPropValid extends EntityJob { @@ -54,75 +53,21 @@ public class CheckPropValid extends EntityJob { if (property.getEntityStatus() == EntityStatus.QUALIFIED) { // this property is to be tested. - // does this property have an id at all? - if (property.hasId()) { - if (!property.getId().isTemporary()) { - - final EntityInterface abstractProperty = - retrieveValidSparseEntityById(property.getId(), null); - - assertAllowedToUse(abstractProperty); - - deriveOverrideStatus(property, abstractProperty); - continue; - } else { - // task here: find the corresponding abstract - // property (or rarely any other entity) in this - // container which has the same (negative) id. - - // fetch the abstractProperty from the container. - final EntityInterface abstractProperty = getEntityById(property.getId()); - - if (abstractProperty != null) { - assertAllowedToUse(abstractProperty); - - // link the id of the property to the id of - // the abstractProperty means. This has the - // effect that the property will have a - // valid id as soon as the abstractProperty - // has been inserted. - property.linkIdToEntity(abstractProperty); - deriveOverrideStatus(property, abstractProperty); - continue; - } else if (!property.hasName()) { - // an abstractProperty with this (negative) id - // had not been found in this container. - throw ENTITY_DOES_NOT_EXIST; - } - } - } - - if (property.hasName()) { - - // try and get it from the container - EntityInterface foreign = getEntityByName(property.getName()); - if (foreign != null) { - assertAllowedToUse(foreign); - - // link the id of the property to the id of - // the abstractProperty means. This has the - // effect that the property will have a - // valid id as soon as the abstractProperty - // has been inserted. - property.linkIdToEntity(foreign); - deriveOverrideStatus(property, foreign); - } else { - foreign = retrieveValidSparseEntityByName(property.getName()); - - assertAllowedToUse(foreign); - - property.setId(foreign.getId()); - - deriveOverrideStatus(property, foreign); - } - } - if (!property.hasName() && !property.hasId()) { // The property has neither an id nor a name. // Thus it cannot be identified. throw ServerMessages.ENTITY_HAS_NO_NAME_OR_ID; } + EntityInterface abstractProperty = + resolve(property.getId(), property.getName(), (String) null); + if (abstractProperty == null) { + property.addError(ENTITY_DOES_NOT_EXIST); + continue; + } + assertAllowedToUse(abstractProperty); + deriveOverrideStatus(property, abstractProperty); + property.linkIdToEntity(abstractProperty); } } catch (final Message m) { property.addError(m); 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 0b8ba45b1cae19812641ed733be7aacc2fb78575..7b0a9977680c378db98dca4d3e7ee31dba06e840 100644 --- a/src/main/java/org/caosdb/server/jobs/core/CheckRefidIsaParRefid.java +++ b/src/main/java/org/caosdb/server/jobs/core/CheckRefidIsaParRefid.java @@ -1,9 +1,10 @@ /* - * ** header v3.0 * This file is a part of the CaosDB Project. * * Copyright (C) 2018 Research Group Biomedical Physics, * Max-Planck-Institute for Dynamics and Self-Organization Göttingen + * Copyright (C) 2023 IndiScale GmbH <info@indiscale.com> + * Copyright (C) 2023 Timm Fitschen <t.fitschen@indiscale.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -17,8 +18,6 @@ * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. - * - * ** end header */ package org.caosdb.server.jobs.core; @@ -41,10 +40,11 @@ import org.caosdb.server.utils.Observer; import org.caosdb.server.utils.ServerMessages; /** - * Check if the referenced entity is in the scope of the data type. E.g. if the data type is - * 'Person' the referenced entity is to be a child of 'Person'. + * Check if the referenced entity is in the scope of the data type. + * + * <p>E.g. if the data type is 'Person' the referenced entity is to be a child of 'Person'. * - * @author tf + * @author Timm Fitschen <t.fitschen@indiscale.com> */ public class CheckRefidIsaParRefid extends EntityJob implements Observer { @@ -82,24 +82,8 @@ public class CheckRefidIsaParRefid extends EntityJob implements Observer { for (final IndexedSingleValue v : vals) { if (v != null && v.getWrapped() != null) { final ReferenceValue rv = (ReferenceValue) v.getWrapped(); - if (rv.getEntity() != null - && rv.getEntity().hasRole() - && rv.getEntity().getRole() == Role.File) { - } else if (rv.getId() != null - && rv.getId().isTemporary() - && getEntityById(rv.getId()) != null - && getEntityById(rv.getId()).getRole() == Role.File) { - } else if (rv.getId() == null - && rv.getName() != null - && getEntityByName(rv.getName()) != null - && getEntityByName(rv.getName()).getRole() == Role.File) { - } else if (rv.getId() != null - && !rv.getId().isTemporary() - && retrieveValidSparseEntityById(rv.getId(), rv.getVersion()).getRole() - == Role.File) { - } else if (rv.getName() != null - && retrieveValidSparseEntityByName(rv.getName()).getRole() == Role.File) { - } else { + EntityInterface referencedEntity = resolve(rv.getId(), rv.getName(), rv.getVersion()); + if (referencedEntity.getRole() != Role.File) { throw ServerMessages.REFERENCE_IS_NOT_ALLOWED_BY_DATATYPE; } } 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 12b545b8e6fd608e20851d37f34e809acfcd1544..78d257eadf4fa1de4331bd9bf38855ca04ccdd9e 100644 --- a/src/main/java/org/caosdb/server/jobs/core/CheckRefidValid.java +++ b/src/main/java/org/caosdb/server/jobs/core/CheckRefidValid.java @@ -1,11 +1,10 @@ /* - * ** header v3.0 * This file is a part of the CaosDB Project. * * Copyright (C) 2018 Research Group Biomedical Physics, * Max-Planck-Institute for Dynamics and Self-Organization Göttingen - * Copyright (C) 2020-2021 IndiScale GmbH <info@indiscale.com> - * Copyright (C) 2020-2021 Timm Fitschen <t.fitschen@indiscale.com> + * Copyright (C) 2020-2021,2023 IndiScale GmbH <info@indiscale.com> + * Copyright (C) 2020-2021,2023 Timm Fitschen <t.fitschen@indiscale.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -19,8 +18,6 @@ * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. - * - * ** end header */ package org.caosdb.server.jobs.core; @@ -31,6 +28,7 @@ import org.caosdb.server.datatype.CollectionValue; import org.caosdb.server.datatype.IndexedSingleValue; import org.caosdb.server.datatype.ReferenceValue; import org.caosdb.server.entity.Entity; +import org.caosdb.server.entity.EntityID; import org.caosdb.server.entity.EntityInterface; import org.caosdb.server.entity.Message; import org.caosdb.server.jobs.EntityJob; @@ -43,7 +41,7 @@ import org.caosdb.server.utils.ServerMessages; /** * Check whether a reference property is pointing to a valid entity. * - * @author tf + * @author Timm Fitschen <t.fitschen@indiscale.com> */ public class CheckRefidValid extends EntityJob implements Observer { @Override @@ -83,61 +81,16 @@ public class CheckRefidValid extends EntityJob implements Observer { } private void checkRefValue(final ReferenceValue ref) throws Message { - if (ref.getId() != null) { - if (!ref.getId().isTemporary()) { - final EntityInterface referencedValidEntity = - retrieveValidSparseEntityById(ref.getId(), ref.getVersion()); - assertAllowedToUse(referencedValidEntity); - - // link the entity as versioned entity iff the reference specified a version - ref.setEntity(referencedValidEntity, ref.getVersion() != null); - - } else { - - // is the referenced entity yet linked to this refid - // property? - if (ref.getEntity() == null) { - - // link the entity with the corresponding - // negative id to this reference object - final EntityInterface referencedEntity = getEntityById(ref.getId()); - if (referencedEntity != null) { - assertAllowedToUse(referencedEntity); - - // link the entity as versioned entity iff the reference specified a version - ref.setEntity(referencedEntity, ref.getVersion() != null); - } else { - throw ServerMessages.REFERENCED_ENTITY_DOES_NOT_EXIST; - } - } - ref.getEntity().acceptObserver(this); - checkRefEntity(ref); - } - } else if (ref.getName() != null) { - // is the referenced entity yet linked to this - // refid property? - if (ref.getEntity() == null) { - // the entity is in this container? - final EntityInterface referencedEntity = getEntityByName(ref.getName()); - - if (referencedEntity != null) { - assertAllowedToUse(referencedEntity); - - // link the entity as versioned entity iff the reference specified a version - ref.setEntity(referencedEntity, ref.getVersion() != null); - if (checkRefEntity(ref)) { - ref.getEntity().acceptObserver(this); - } - } else { - final EntityInterface referencedValidEntity = - retrieveValidSparseEntityByName(ref.getName()); - assertAllowedToUse(referencedValidEntity); - - // link the entity as versioned entity iff the reference specified a version - ref.setEntity(referencedValidEntity, ref.getVersion() != null); - } - } + EntityID refId = ref.getId(); + if (refId == null) { + refId = new EntityID(ref.getName()); + } + EntityInterface referencedEntity = resolve(refId, ref.getName(), ref.getVersion()); + if (referencedEntity == null) { + throw ServerMessages.REFERENCED_ENTITY_DOES_NOT_EXIST; } + assertAllowedToUse(referencedEntity); + ref.setEntity(referencedEntity, ref.getVersion() != null); } private void assertAllowedToUse(final EntityInterface referencedEntity) { diff --git a/src/main/java/org/caosdb/server/jobs/core/EntityStateJob.java b/src/main/java/org/caosdb/server/jobs/core/EntityStateJob.java index a4c50f8893935d7bbd6091b970b32a8811a08d93..becc6499c1c1b5c8e236da12c9531a28490d634c 100644 --- a/src/main/java/org/caosdb/server/jobs/core/EntityStateJob.java +++ b/src/main/java/org/caosdb/server/jobs/core/EntityStateJob.java @@ -1,8 +1,8 @@ /* * 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> + * Copyright (C) 2020,2023 IndiScale GmbH <info@indiscale.com> + * Copyright (C) 2020,2023 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 @@ -53,6 +53,7 @@ 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.jobs.LazyEntityResolver; import org.caosdb.server.permissions.EntityACI; import org.caosdb.server.permissions.EntityACL; import org.caosdb.server.query.Query; @@ -572,7 +573,7 @@ public abstract class EntityStateJob extends EntityJob { final String key = "transition" + refid.toString(); EntityInterface transition = getCached(key); if (transition == null) { - transition = retrieveValidEntity(refid); + transition = resolve(refid); putCache(key, transition); } result.add(new Transition(transition)); @@ -659,7 +660,7 @@ public abstract class EntityStateJob extends EntityJob { private EntityInterface retrieveStateEntity(final String stateName) throws Message { try { - return retrieveValidEntity(retrieveValidIDByName(stateName)); + return resolve(null, stateName, (String) null); } catch (final EntityDoesNotExistException e) { throw STATE_NOT_IN_STATE_MODEL; } @@ -667,7 +668,7 @@ public abstract class EntityStateJob extends EntityJob { private EntityInterface retrieveStateModelEntity(final String stateModel) throws Message { try { - return retrieveValidEntity(retrieveValidIDByName(stateModel)); + return resolve(null, stateModel, (String) null); } catch (final EntityDoesNotExistException e) { throw STATE_MODEL_NOT_FOUND; } @@ -676,7 +677,7 @@ public abstract class EntityStateJob extends EntityJob { protected EntityInterface getStateRecordType() throws Message { EntityInterface stateRecordType = getCached(STATE_RECORD_TYPE_NAME); if (stateRecordType == null) { - stateRecordType = retrieveValidSparseEntityByName(STATE_RECORD_TYPE_NAME); + stateRecordType = resolve(null, STATE_RECORD_TYPE_NAME, (String) null); putCache(STATE_RECORD_TYPE_NAME, stateRecordType); } return stateRecordType; @@ -797,7 +798,7 @@ public abstract class EntityStateJob extends EntityJob { EntityInterface stateEntity = getCached(key); if (stateEntity == null) { - stateEntity = retrieveValidEntity(refid.getId()); + stateEntity = resolve(refid.getId()); putCache(key, stateEntity); } @@ -832,9 +833,11 @@ public abstract class EntityStateJob extends EntityJob { + " WHICH REFERENCES " + stateEntity.getId().toString(), getUser(), - c); - query.execute(getTransaction().getAccess()); - result = retrieveValidEntity(c.get(0).getId()); + c, + getTransaction(), + getTransaction().getAccess()); + query.execute(); + result = resolve(c.get(0).getId()); putCache(key, result); return result; } @@ -852,6 +855,11 @@ public abstract class EntityStateJob extends EntityJob { if (value instanceof DeleteEntity) { throw new RuntimeException("Delete entity in cache. This is an implementation error."); } + if (value instanceof LazyEntityResolver) { + // resolve immediately, otherwise the access might be released when + // the object is being resolved. + ((LazyEntityResolver) value).resolveAll(); + } id_in_cache.add(value.getId()); cache.put(key, value); } 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 114fb5f2a7e618cb5aeb7f77e68cf31584a899cf..7c233e3cd80c18a3ef5d12554d9a18fe55f4d2c0 100644 --- a/src/main/java/org/caosdb/server/jobs/core/ExecuteQuery.java +++ b/src/main/java/org/caosdb/server/jobs/core/ExecuteQuery.java @@ -31,22 +31,26 @@ import org.caosdb.server.jobs.JobAnnotation; import org.caosdb.server.jobs.TransactionStage; import org.caosdb.server.query.Query; import org.caosdb.server.query.Query.ParsingException; +import org.caosdb.server.transaction.Retrieve; +import org.caosdb.server.utils.EntityStatus; import org.caosdb.server.utils.ServerMessages; -@JobAnnotation(flag = "query", stage = TransactionStage.INIT) +@JobAnnotation(flag = "query", stage = TransactionStage.INIT, transaction = Retrieve.class) public class ExecuteQuery extends FlagJob { @Override protected void job(final String value) { if (value != null) { - final Query queryInstance = - new Query(value, getTransaction().getTransactor(), getContainer()); + // run paging job first + getTransaction().getSchedule().runJob(null, Paging.class); + + final Query queryInstance = new Query(value, getTransaction()); getContainer().setQuery(queryInstance); try { - queryInstance.execute(getTransaction().getAccess()); + queryInstance.execute(); } catch (final ParsingException e) { - getContainer().addError(ServerMessages.QUERY_PARSING_ERROR); + getContainer().addError(ServerMessages.QUERY_PARSING_ERROR(e.getMessage())); } catch (final UnsupportedOperationException e) { getContainer().addError(ServerMessages.QUERY_EXCEPTION); getContainer() @@ -57,10 +61,25 @@ public class ExecuteQuery extends FlagJob { getContainer() .getFlags() .getOrDefault(RegisterFileDownload.QUERY_FLAG, RegisterFileDownload.FALSE); + + int startIndex = 0; + int endIndex = getContainer().size(); + + if (((Retrieve) getTransaction()).hasPaging()) { + Retrieve.Paging paging = ((Retrieve) getTransaction()).getPaging(); + startIndex = Math.min(getContainer().size(), paging.startIndex); + endIndex = Math.min(getContainer().size(), paging.endIndex); + } + + int ii = 0; for (final EntityInterface entity : getContainer()) { - // trigger (or don't) the download of the file (only if file entity, ofc). - entity.setFlag(RegisterFileDownload.FLAG, downloadFlag); - getTransaction().getSchedule().addAll(loadJobs(entity, getTransaction())); + if (ii >= startIndex && ii < endIndex) { + entity.setFlag(RegisterFileDownload.FLAG, downloadFlag); + getTransaction().getSchedule().addAll(loadJobs(entity, getTransaction())); + } else { + entity.setEntityStatus(EntityStatus.IGNORE); + } + ii++; } } } diff --git a/src/main/java/org/caosdb/server/jobs/core/GenerateEntityId.java b/src/main/java/org/caosdb/server/jobs/core/GenerateEntityId.java new file mode 100644 index 0000000000000000000000000000000000000000..e3b21d9bdf67ad7a1ebe23ea1b77816e72b12de3 --- /dev/null +++ b/src/main/java/org/caosdb/server/jobs/core/GenerateEntityId.java @@ -0,0 +1,50 @@ +/* + * This file is a part of the CaosDB Project. + * + * Copyright (C) 2023 IndiScale GmbH <info@indiscale.com> + * Copyright (C) 2023 Timm Fitschen <t.fitschen@indiscale.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + */ +package org.caosdb.server.jobs.core; + +import org.caosdb.server.entity.EntityInterface; +import org.caosdb.server.entity.InsertEntity; +import org.caosdb.server.entity.Message; +import org.caosdb.server.jobs.EntityJob; +import org.caosdb.server.jobs.JobAnnotation; +import org.caosdb.server.jobs.TransactionStage; +import org.caosdb.server.transaction.WriteTransactionInterface; +import org.caosdb.server.utils.EntityStatus; + +/** + * Generates entity ids for newly inserted entities. + * + * @author Timm Fitschen <t.fitschen@indiscale.com> + */ +@JobAnnotation(stage = TransactionStage.PRE_TRANSACTION) +public class GenerateEntityId extends EntityJob { + + @Override + protected void run() throws Message { + EntityInterface entity = getEntity(); + if (entity instanceof InsertEntity + && getTransaction() instanceof WriteTransactionInterface + && entity.getEntityStatus() == EntityStatus.QUALIFIED) { + String id = ((WriteTransactionInterface) getTransaction()).generateId(); + entity.setId(id); + } + } +} 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 a1bd800c7ca40b28d7b9ff27dd8f828ed112891f..70aea11281774230e90e586cad3e897426df8e83 100644 --- a/src/main/java/org/caosdb/server/jobs/core/Inheritance.java +++ b/src/main/java/org/caosdb/server/jobs/core/Inheritance.java @@ -1,11 +1,10 @@ /* - * ** header v3.0 * This file is a part of the CaosDB Project. * * Copyright (C) 2018 Research Group Biomedical Physics, * Max-Planck-Institute for Dynamics and Self-Organization Göttingen - * Copyright (C) 2019-2021 IndiScale GmbH <info@indiscale.com> - * Copyright (C) 2019-2021 Timm Fitschen <t.fitschen@indiscale.com> + * Copyright (C) 2019-2021,2023 IndiScale GmbH <info@indiscale.com> + * Copyright (C) 2019-2021,2023 Timm Fitschen <t.fitschen@indiscale.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -19,20 +18,16 @@ * * 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 org.caosdb.api.entity.v1.MessageCode; -import org.caosdb.server.database.backend.transaction.RetrieveFullEntityTransaction; 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.RetrieveEntity; import org.caosdb.server.entity.StatementStatus; import org.caosdb.server.entity.UpdateEntity; import org.caosdb.server.entity.wrapper.Property; @@ -88,12 +83,7 @@ public class Inheritance extends EntityJob { runJobFromSchedule(getEntity(), CheckParValid.class); // try to get the parent entity from the current transaction container - EntityInterface foreign = getEntityByName(parent.getName()); - if (foreign == null) { - // was not in container -> retrieve from database. - execute(new RetrieveFullEntityTransaction(parent)); - foreign = parent; - } + EntityInterface foreign = resolve(parent); collectInheritedProperties(transfer, foreign, inheritance); } catch (final IllegalArgumentException e) { @@ -136,26 +126,24 @@ public class Inheritance extends EntityJob { break propertyLoop; } - EntityInterface validProperty = new RetrieveEntity(property.getId()); + EntityInterface abstractProperty = null; if (getEntity().hasParents()) { outer: for (EntityInterface par : getEntity().getParents()) { - if (!par.hasProperties()) { - par = resolve(par); - } + par = resolve(par); for (final EntityInterface prop : par.getProperties()) { - if (validProperty.hasId() && validProperty.getId().equals(prop.getId())) { - validProperty = prop; + if ((property.hasId() && property.getId().equals(prop.getId())) + || property.hasName() && property.getName().equals(prop.getName())) { + abstractProperty = prop; break outer; } } } - } else { - execute(new RetrieveFullEntityTransaction(validProperty)); } - if (validProperty.getEntityStatus() == EntityStatus.VALID) { - collectInheritedProperties(transfer, validProperty, inheritance); + if (abstractProperty == null) { + abstractProperty = resolve(property); } + collectInheritedProperties(transfer, abstractProperty, inheritance); } catch (final IllegalArgumentException e) { property.addWarning(ILLEGAL_INHERITANCE_MODE); break propertyLoop; diff --git a/src/main/java/org/caosdb/server/jobs/core/NoCache.java b/src/main/java/org/caosdb/server/jobs/core/NoCache.java index e408330ee2639d1c5d7dc5f145848e292daa9b3b..78b54f6c7ecf97669889097241a9ee5f595a9416 100644 --- a/src/main/java/org/caosdb/server/jobs/core/NoCache.java +++ b/src/main/java/org/caosdb/server/jobs/core/NoCache.java @@ -1,9 +1,10 @@ /* - * ** header v3.0 * This file is a part of the CaosDB Project. * * Copyright (C) 2018 Research Group Biomedical Physics, * Max-Planck-Institute for Dynamics and Self-Organization Göttingen + * Copyright (C) 2023 IndiScale GmbH <info@indiscale.com> + * Copyright (C) 2023 Timm Fitschen <t.fitschen@indiscale.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -17,8 +18,6 @@ * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. - * - * ** end header */ package org.caosdb.server.jobs.core; @@ -26,6 +25,13 @@ import org.caosdb.server.jobs.FlagJob; import org.caosdb.server.jobs.JobAnnotation; import org.caosdb.server.jobs.TransactionStage; +/** + * Turn off caching, just for the current transaction. + * + * <p>If the caching is disabled globally this has no effect. + * + * @author Timm Fitschen <t.fitschen@indiscale.com> + */ @JobAnnotation( flag = "cache", defaultValue = "true", @@ -37,6 +43,6 @@ public class NoCache extends FlagJob { @Override protected void job(final String value) { - getTransaction().getAccess().setUseCache(Boolean.valueOf(value)); + getTransaction().setUseCache(Boolean.valueOf(value)); } } diff --git a/src/main/java/org/caosdb/server/jobs/core/Paging.java b/src/main/java/org/caosdb/server/jobs/core/Paging.java index 5f2a6ed62f48f20d09c34c8ee34190231903ffe7..aa918ecfe2ac20b90b019b5ace91bde05ccac9ef 100644 --- a/src/main/java/org/caosdb/server/jobs/core/Paging.java +++ b/src/main/java/org/caosdb/server/jobs/core/Paging.java @@ -31,7 +31,7 @@ import org.caosdb.server.jobs.TransactionStage; import org.caosdb.server.transaction.Retrieve; import org.caosdb.server.utils.EntityStatus; -@JobAnnotation(flag = "P", stage = TransactionStage.PRE_TRANSACTION) +@JobAnnotation(flag = "P", stage = TransactionStage.INIT, transaction = Retrieve.class) public class Paging extends FlagJob { public static final int DEFAULT_LENGTH = 100; @@ -42,20 +42,24 @@ public class Paging extends FlagJob { protected void job(final String value) { if (getTransaction() instanceof Retrieve) { if (value != null) { - int index1 = DEFAULT_INDEX; - int index2 = index1 + DEFAULT_LENGTH; + int startIndex = DEFAULT_INDEX; + int endIndex = startIndex + DEFAULT_LENGTH; final Matcher m = pattern.matcher(value); if (m.matches()) { if (m.group(1) != null) { - index1 = Integer.parseInt(m.group(1)); + startIndex = Integer.parseInt(m.group(1)); } if (m.group(2) != null) { - index2 = index1 + Integer.parseInt(m.group(2)); + endIndex = startIndex + Integer.parseInt(m.group(2)); } } + + // this info may be used by other jobs + ((Retrieve) getTransaction()).setPaging(startIndex, endIndex); + int i = 0; for (final EntityInterface e : getContainer()) { - if (i >= index2 || i < index1) { + if (i >= endIndex || i < startIndex) { // do not retrieve this entity e.setEntityStatus(EntityStatus.IGNORE); } 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 7196f04edc99cf1269603e820975a96703639965..ecac050455b385d2ecf27bb4ff5d39d46c941508 100644 --- a/src/main/java/org/caosdb/server/jobs/core/ProcessNameProperties.java +++ b/src/main/java/org/caosdb/server/jobs/core/ProcessNameProperties.java @@ -1,9 +1,10 @@ /* - * ** header v3.0 * This file is a part of the CaosDB Project. * * Copyright (C) 2018 Research Group Biomedical Physics, * Max-Planck-Institute for Dynamics and Self-Organization Göttingen + * Copyright (C) 2023 IndiScale GmbH <info@indiscale.com> + * Copyright (C) 2023 Timm Fitschen <t.fitschen@indiscale.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -17,17 +18,16 @@ * * 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 static org.caosdb.server.entity.MagicTypes.NAME; -import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; +import java.util.LinkedList; import java.util.Set; +import org.caosdb.server.database.exceptions.EntityDoesNotExistException; import org.caosdb.server.datatype.AbstractDatatype; import org.caosdb.server.entity.EntityID; import org.caosdb.server.entity.EntityInterface; @@ -41,9 +41,13 @@ import org.caosdb.server.utils.EntityStatus; import org.caosdb.server.utils.ServerMessages; /** - * To be called after CheckPropValid and Inheritance. + * Checks if a property is a subproperty of "name" and flags the entity accordingly. + * + * <p>This enables a different behavior in the search. * - * @author tf + * <p>To be called after CheckPropValid and Inheritance. + * + * @author Timm Fitschen <t.fitschen@indiscale.com> */ @JobAnnotation(stage = TransactionStage.PRE_TRANSACTION) public class ProcessNameProperties extends EntityJob { @@ -133,21 +137,22 @@ public class ProcessNameProperties extends EntityJob { } private Collection<Object> getNearestValidParents(final Property prop) { - if (prop.hasId() && !prop.getId().isTemporary()) { - final ArrayList<Object> ret = new ArrayList<Object>(); - if (prop.hasParents()) { - for (final Parent par : prop.getParents()) { + if (prop.hasParents()) { + final Collection<Object> ret = new LinkedList<Object>(); + for (final Parent par : prop.getParents()) { + if (par.hasId()) { ret.add(par.getId()); } - } else { - ret.add(prop.getId()); } return ret; } else if (prop.hasId()) { - // property is new -> get valid parents of any depth - final EntityInterface propertyEntity = getEntityById(prop.getId()); - if (propertyEntity != null) { - return getNearestValidParents(propertyEntity); + try { + EntityInterface propertyEntity = resolve(prop.getId()); + if (propertyEntity != null) { + return getNearestValidParents(propertyEntity); + } + } catch (EntityDoesNotExistException e) { + // return null } return null; } else { diff --git a/src/main/java/org/caosdb/server/jobs/core/ResolveNames.java b/src/main/java/org/caosdb/server/jobs/core/ResolveNames.java index 5ab722c7176e89485e2bd40dfd515a77bc09e38b..e353375baaaa11c59d7e67c7e39c8cd5a8eb38ee 100644 --- a/src/main/java/org/caosdb/server/jobs/core/ResolveNames.java +++ b/src/main/java/org/caosdb/server/jobs/core/ResolveNames.java @@ -1,9 +1,10 @@ /* - * ** header v3.0 * This file is a part of the CaosDB Project. * * Copyright (C) 2018 Research Group Biomedical Physics, * Max-Planck-Institute for Dynamics and Self-Organization Göttingen + * Copyright (C) 2023 IndiScale GmbH <info@indiscale.com> + * Copyright (C) 2023 Timm Fitschen <t.fitschen@indiscale.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -17,14 +18,13 @@ * * 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 org.caosdb.server.database.backend.transaction.GetIDByName; +import org.caosdb.server.database.backend.transaction.RetrieveSparseEntity; import org.caosdb.server.database.exceptions.EntityDoesNotExistException; import org.caosdb.server.entity.Entity; import org.caosdb.server.entity.EntityID; @@ -35,6 +35,18 @@ import org.caosdb.server.jobs.ContainerJob; import org.caosdb.server.utils.EntityStatus; import org.caosdb.server.utils.ServerMessages; +/** + * Resolve the names to ids for all entities in the current retrieval. + * + * <p>If an entity has a name which looks like an id: Check whether that is an existing id first. + * + * <p>Otherwise, search via the name. + * + * <p>It there are multiple matching entities, add all of them to the current container. The clients + * may handle the ambiguity. + * + * @author Timm Fitschen <t.fitschen@indiscale.com> + */ public class ResolveNames extends ContainerJob { @Override @@ -45,6 +57,20 @@ public class ResolveNames extends ContainerJob { public void resolve(final TransactionContainer container) { final ArrayList<Entity> add = new ArrayList<Entity>(); for (final EntityInterface e : container) { + if (!e.hasId() && e.hasName() && getTransaction().matchIdPattern(e.getName())) { + try { + EntityInterface valid = + execute( + new RetrieveSparseEntity( + new EntityID(e.getName()), + e.hasVersion() ? e.getVersion().getId() : null)) + .getEntity(); + e.setId(valid.getId()); + } catch (final EntityDoesNotExistException exc) { + // Definitely not an existing id. + } + } + if (e.hasName() && !e.hasId()) { try { final List<EntityID> c = execute(new GetIDByName(e.getName(), false)).getList(); 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 7cd7791e0b27f1ec1d5e67e047e7f1cbe177948c..a89385e29f458ad3ad15eea0d9e6b6bf7640bca6 100644 --- a/src/main/java/org/caosdb/server/jobs/core/RetrieveAllJob.java +++ b/src/main/java/org/caosdb/server/jobs/core/RetrieveAllJob.java @@ -27,19 +27,43 @@ import org.caosdb.server.entity.EntityInterface; import org.caosdb.server.jobs.FlagJob; import org.caosdb.server.jobs.JobAnnotation; import org.caosdb.server.jobs.TransactionStage; +import org.caosdb.server.transaction.Retrieve; +import org.caosdb.server.utils.EntityStatus; -@JobAnnotation(flag = "all", stage = TransactionStage.INIT) +@JobAnnotation(flag = "all", stage = TransactionStage.INIT, transaction = Retrieve.class) public class RetrieveAllJob extends FlagJob { @Override protected void job(String value) { if (getContainer().isEmpty()) { + // run paging job first + getTransaction().getSchedule().runJob(null, Paging.class); + if (value == null) { value = "ENTITY"; } + execute(new RetrieveAll(getContainer(), value)); + + int startIndex = 0; + int endIndex = getContainer().size(); + + if (((Retrieve) getTransaction()).hasPaging()) { + startIndex = + Math.min(getContainer().size(), ((Retrieve) getTransaction()).getPaging().startIndex); + endIndex = + Math.min(getContainer().size(), ((Retrieve) getTransaction()).getPaging().endIndex); + } + + // only add jobs for those which are on this page + int ii = 0; for (EntityInterface entity : getContainer()) { - getTransaction().getSchedule().addAll(loadJobs(entity, getTransaction())); + if (ii >= startIndex && ii < endIndex) { + getTransaction().getSchedule().addAll(loadJobs(entity, getTransaction())); + } else { + entity.setEntityStatus(EntityStatus.IGNORE); + } + ii++; } } } diff --git a/src/main/java/org/caosdb/server/jobs/core/UniqueName.java b/src/main/java/org/caosdb/server/jobs/core/UniqueName.java index 86e6c37e35ec91db647e14e30896abb6b884555b..079afdd9a922fa5758e5f4eb6bec80538338db21 100644 --- a/src/main/java/org/caosdb/server/jobs/core/UniqueName.java +++ b/src/main/java/org/caosdb/server/jobs/core/UniqueName.java @@ -40,7 +40,7 @@ public class UniqueName extends FlagJob { // check against data base try { - final EntityID foreign = retrieveValidIDByName(entity.getName()); + final EntityID foreign = retrieveValidIDByName(entity.getName(), null); if (entity.hasId() && !foreign.equals(entity.getId())) { throw new EntityWasNotUniqueException(); } diff --git a/src/main/java/org/caosdb/server/logging/log4j/CustomConfigurationFactory.java b/src/main/java/org/caosdb/server/logging/log4j/CustomConfigurationFactory.java index 62722317f882dffae4168808514a6d972ae8bfe6..4416227f9672a9b3be3ffb34306e78de04a20a2f 100644 --- a/src/main/java/org/caosdb/server/logging/log4j/CustomConfigurationFactory.java +++ b/src/main/java/org/caosdb/server/logging/log4j/CustomConfigurationFactory.java @@ -54,7 +54,7 @@ public class CustomConfigurationFactory extends PropertiesConfigurationFactory { LOGGER.debug("Reconfiguration is done by {}", getClass().toString()); List<String> sources = getConfigFiles(); - if (sources.size() > 1) { + if (sources.size() > 0) { final List<AbstractConfiguration> configs = new ArrayList<>(); for (final String sourceLocation : sources) { LOGGER.debug("Reconfigure with {}", sourceLocation); diff --git a/src/main/java/org/caosdb/server/query/Backreference.java b/src/main/java/org/caosdb/server/query/Backreference.java index 7d551fd45e761a54d2c2508dcbb1b98ffc72891c..b801ffaf396a3ba9f98531ca06fc274b05c70b96 100644 --- a/src/main/java/org/caosdb/server/query/Backreference.java +++ b/src/main/java/org/caosdb/server/query/Backreference.java @@ -23,7 +23,7 @@ package org.caosdb.server.query; import static java.sql.Types.VARCHAR; -import static org.caosdb.server.database.DatabaseUtils.bytes2UTF8; +import static org.caosdb.server.database.backend.implementation.MySQL.DatabaseUtils.bytes2UTF8; import java.sql.CallableStatement; import java.sql.Connection; diff --git a/src/main/java/org/caosdb/server/query/CQLLexer.g4 b/src/main/java/org/caosdb/server/query/CQLLexer.g4 index 7a2fda860485fc366d4b7aa8d3381985f806387f..974a8223e973e8e2c00ac90877621ff541581f12 100644 --- a/src/main/java/org/caosdb/server/query/CQLLexer.g4 +++ b/src/main/java/org/caosdb/server/query/CQLLexer.g4 @@ -443,7 +443,7 @@ WHITE_SPACE_f: /** */ WHITE_SPACE: - [ \t\n\r]+ + WHITE_SPACE_f ; /** */ @@ -532,31 +532,55 @@ TODAY: [Tt][Oo][Dd][Aa][Yy] WHITE_SPACE_f? ; +/** */ +COLON: + ':' +; + +/** Matches signed and unsigned numbers with decimal points, also numbers in scientific notation. */ +DECIMAL_NUMBER: + ((HYPHEN_f | PLUS ) WHITE_SPACE_f?)? + ( NUM_f? DOT NUM_f (WHITE_SPACE_f? E_NOTATION_f)? + | NUM_f (WHITE_SPACE_f? E_NOTATION_f)? + ) +; + /** */ HYPHEN: - '-' + HYPHEN_f ; /** */ -COLON: - ':' +PLUS: + '+' ; /** */ -NUM: +fragment +HYPHEN_f: + '-' +; + +/** Matches only unsigned integer numbers. */ +UNSIGNED_INT: NUM_f - | DOT NUM_f - | NUM_f DOT NUM_f ; /** */ +fragment NUM_f: ('0'..'9')+ ; +/** */ +fragment +E_NOTATION_f: + [Ee] WHITE_SPACE_f? [+-]? WHITE_SPACE_f? NUM_f +; + /** */ TXT: - ('a'..'z' | 'A'..'Z' | '0'..'9' | '_' | '-' {_input.LA(1) != '>'}? | '+' | '&' | ';' | ',' | '$' | ':' | '%' | '^' | '~' {_input.LA(1) != '='}? | '`' | '´' | 'ö' | 'ä' | 'ß' | 'ü' | 'Ö' | 'Ä' | 'Ü' | '@' | '[' | ']' | '{' | '}' )+ + ('a'..'z' | 'A'..'Z' | NUM_f | '_' | '-' {_input.LA(1) != '>'}? | PLUS | '&' | ';' | ',' | '$' | ':' | '%' | '^' | '~' {_input.LA(1) != '='}? | '`' | '´' | 'ö' | 'ä' | 'ß' | 'ü' | 'Ö' | 'Ä' | 'Ü' | '@' | '[' | ']' | '{' | '}' )+ ; /** */ diff --git a/src/main/java/org/caosdb/server/query/CQLParser.g4 b/src/main/java/org/caosdb/server/query/CQLParser.g4 index db84e8a2a86064569b43ce239d02b6b31a29f45b..6876ab965f2c2e69f35aa1820e88744886f7030d 100644 --- a/src/main/java/org/caosdb/server/query/CQLParser.g4 +++ b/src/main/java/org/caosdb/server/query/CQLParser.g4 @@ -135,6 +135,7 @@ entity_filter returns [EntityFilterInterface filter] ( ( LPAREN WHITE_SPACE? + (HAS_A (PROPERTY)?)? ( filter_expression {$filter = $filter_expression.efi;} | conjunction {$filter = $conjunction.c;} @@ -283,20 +284,20 @@ transaction_time returns [String tqp, String op] */ datetime : - NUM // year + UNSIGNED_INT // year ( - HYPHEN NUM // mon + HYPHEN UNSIGNED_INT // mon ( - HYPHEN NUM // day of mon + HYPHEN UNSIGNED_INT // day of mon ( (m=TXT {$m.text.equals("T")}?)?// compliance with iso datetime - NUM // hour + UNSIGNED_INT // hour ( - COLON NUM // minut + COLON UNSIGNED_INT // minut ( - COLON NUM // sec + COLON UNSIGNED_INT // sec ( - DOT NUM // millisec + DOT UNSIGNED_INT // millisec )? )? )? @@ -435,6 +436,7 @@ conjunction returns [Conjunction c] f1 = filter_expression {$c.add($f1.efi);} | LPAREN WHITE_SPACE? + (HAS_A (PROPERTY)?)? ( f4 = filter_expression {$c.add($f4.efi);} | disjunction {$c.add($disjunction.d);} @@ -475,6 +477,7 @@ disjunction returns [Disjunction d] f1 = filter_expression {$d.add($f1.efi);} | LPAREN WHITE_SPACE? + (HAS_A (PROPERTY)?)? ( f4 = filter_expression {$d.add($f4.efi);} | conjunction {$d.add($conjunction.c);} @@ -580,7 +583,7 @@ property returns [Query.Pattern pp, String agg]locals [StringBuffer sb] | like_pattern {$pp = $like_pattern.ep;} | ( double_quoted {$pp = $double_quoted.ep;} ) | ( single_quoted {$pp = $single_quoted.ep;} ) - | ((m=TXT | m=NUM | m=REGEXP_MARKER | m=ENTITY){$sb.append($m.text);})+ {$pp = new Query.Pattern($sb.toString(), Query.Pattern.TYPE_NORMAL);} + | ((m=TXT | m=UNSIGNED_INT | m=UNSIGNED_DECIMAL_NUMBER | m=REGEXP_MARKER | m=ENTITY){$sb.append($m.text);})+ {$pp = new Query.Pattern($sb.toString(), Query.Pattern.TYPE_NORMAL);} ) WHITE_SPACE? ; @@ -612,9 +615,8 @@ value returns [String str] */ number_with_unit : - HYPHEN?? - NUM - (WHITE_SPACE?? unit)? + ( UNSIGNED_INT | DECIMAL_NUMBER | ( HYPHEN | PLUS ) WHITE_SPACE? UNSIGNED_INT) + (WHITE_SPACE? unit)? ; /** @@ -625,7 +627,7 @@ unit (~(WHITE_SPACE | WHICH | HAS_A | WITH_A | WHERE | DOT | AND | OR | RPAREN )) (~(WHITE_SPACE))* | - NUM SLASH (~(WHITE_SPACE))+ + UNSIGNED_INT SLASH (~(WHITE_SPACE))+ ; /** diff --git a/src/main/java/org/caosdb/server/query/Conjunction.java b/src/main/java/org/caosdb/server/query/Conjunction.java index 03b331242e239a26f108973ca7a37624ab80ea64..59dfd57b20ed8bce443e670e767dc42bb91ac6ac 100644 --- a/src/main/java/org/caosdb/server/query/Conjunction.java +++ b/src/main/java/org/caosdb/server/query/Conjunction.java @@ -22,7 +22,7 @@ */ package org.caosdb.server.query; -import static org.caosdb.server.database.DatabaseUtils.bytes2UTF8; +import static org.caosdb.server.database.backend.implementation.MySQL.DatabaseUtils.bytes2UTF8; import java.sql.CallableStatement; import java.sql.Connection; diff --git a/src/main/java/org/caosdb/server/query/Disjunction.java b/src/main/java/org/caosdb/server/query/Disjunction.java index edd2e6daf243cd25ea112fdbfb946f2b80ee80b1..925ef85952e2d31783a6f7447074d0384e508a98 100644 --- a/src/main/java/org/caosdb/server/query/Disjunction.java +++ b/src/main/java/org/caosdb/server/query/Disjunction.java @@ -22,7 +22,7 @@ */ package org.caosdb.server.query; -import static org.caosdb.server.database.DatabaseUtils.bytes2UTF8; +import static org.caosdb.server.database.backend.implementation.MySQL.DatabaseUtils.bytes2UTF8; import java.sql.CallableStatement; import java.sql.Connection; diff --git a/src/main/java/org/caosdb/server/query/IDFilter.java b/src/main/java/org/caosdb/server/query/IDFilter.java index ce01f5f242f914c6f6f812f0875332f7ca004c07..8011139796abff42c0e8109621c849d3e6328137 100644 --- a/src/main/java/org/caosdb/server/query/IDFilter.java +++ b/src/main/java/org/caosdb/server/query/IDFilter.java @@ -1,9 +1,10 @@ /* - * ** header v3.0 * This file is a part of the CaosDB Project. * * Copyright (C) 2018 Research Group Biomedical Physics, * Max-Planck-Institute for Dynamics and Self-Organization Göttingen + * Copyright (C) 2023 IndiScale GmbH <info@indiscale.com> + * Copyright (C) 2023 Timm Fitschen <t.fitschen@indiscale.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -17,8 +18,6 @@ * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. - * - * ** end header */ package org.caosdb.server.query; @@ -32,24 +31,21 @@ import java.sql.SQLException; import org.caosdb.server.query.Query.QueryException; import org.jdom2.Element; +/** + * Applies an ID filter to the query's current result set. + * + * @author Timm Fitschen <t.fitschen@indiscale.com> + */ public class IDFilter implements EntityFilterInterface { private final String operator; private final String value; private final String aggregate; - private final Integer vInt; public IDFilter(final String o, final String v, final String a) { this.operator = o; this.value = v; this.aggregate = a; - Integer i; - try { - i = Integer.valueOf(v); - } catch (final NumberFormatException e) { - i = null; - } - this.vInt = i; } @Override @@ -79,7 +75,7 @@ public class IDFilter implements EntityFilterInterface { if (getValue() == null) { callIDFilter.setNull(4, INTEGER); } else { - callIDFilter.setInt(4, this.vInt); + callIDFilter.setString(4, this.getValue()); } // aggregate diff --git a/src/main/java/org/caosdb/server/query/Negation.java b/src/main/java/org/caosdb/server/query/Negation.java index cfdfd05e286030f525b8d4f3c291e2bdfb27b100..431124df7b995faf6aa493363a51095e7028b9b8 100644 --- a/src/main/java/org/caosdb/server/query/Negation.java +++ b/src/main/java/org/caosdb/server/query/Negation.java @@ -22,7 +22,7 @@ */ package org.caosdb.server.query; -import static org.caosdb.server.database.DatabaseUtils.bytes2UTF8; +import static org.caosdb.server.database.backend.implementation.MySQL.DatabaseUtils.bytes2UTF8; import java.sql.CallableStatement; import java.sql.Connection; diff --git a/src/main/java/org/caosdb/server/query/POV.java b/src/main/java/org/caosdb/server/query/POV.java index 89ea9ad46e6205dfe0583e8f54551effd82a2658..597495f2d16d6e27229660d2341962d1dce20f91 100644 --- a/src/main/java/org/caosdb/server/query/POV.java +++ b/src/main/java/org/caosdb/server/query/POV.java @@ -1,9 +1,10 @@ /* - * ** header v3.0 * This file is a part of the CaosDB Project. * * Copyright (C) 2018 Research Group Biomedical Physics, * Max-Planck-Institute for Dynamics and Self-Organization Göttingen + * Copyright (C) 2023 IndiScale GmbH <info@indiscale.com> + * Copyright (C) 2023 Timm Fitschen <t.fitschen@indiscale.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -17,15 +18,13 @@ * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. - * - * ** end header */ package org.caosdb.server.query; import static java.sql.Types.DOUBLE; import static java.sql.Types.INTEGER; import static java.sql.Types.VARCHAR; -import static org.caosdb.server.database.DatabaseUtils.bytes2UTF8; +import static org.caosdb.server.database.backend.implementation.MySQL.DatabaseUtils.bytes2UTF8; import de.timmfitschen.easyunits.parser.ParserException; import java.sql.CallableStatement; @@ -50,7 +49,15 @@ import org.jdom2.Element; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +/** + * Applies a POV (Property-operator-value) filter to the query's current result set. + * + * @author Timm Fitschen <t.fitschen@indiscale.com> + */ public class POV implements EntityFilterInterface { + public static final Pattern NUMBER_PATTERN = + Pattern.compile( + "^((?:[+-]\\s*)?[0-9]+(?:\\.[0-9]+)?(?:\\s*[eE]\\s*[+-]?\\s*[0-9]+)?)\\s*([^-]*)$"); private SubProperty subp = null; public static int retry = 10; private int retry_count = 0; @@ -116,14 +123,22 @@ public class POV implements EntityFilterInterface { // try and parse as integer try { - final Pattern dp = Pattern.compile("^(-?[0-9]++)\\s*([^(\\.[0-9])-][^-]*)?$"); - final Matcher m = dp.matcher(value); + final Matcher m = NUMBER_PATTERN.matcher(this.value); if (!m.matches()) { throw new NumberFormatException(); } final String vIntStr = m.group(1); + this.vInt = Integer.parseInt(vIntStr.replaceAll("\\s", "")); + if (vIntStr.matches(".*\\s.*")) { + // empty space in scientific notation is a common typo + throw new Query.ParsingException( + "You typed \"" + + vIntStr + + "\". Empty spaces are not allowed in numbers. Did you mean \"" + + vIntStr.replaceAll("\\s", "") + + "\"?"); + } this.unitStr = m.group(2); - this.vInt = Integer.parseInt(vIntStr); } catch (final NumberFormatException e) { this.vInt = null; } @@ -132,15 +147,23 @@ public class POV implements EntityFilterInterface { if (this.vInt == null) { try { // Doubles are allowed without dots, for example when the integer overflows. - final Pattern dp = Pattern.compile("^(-?[0-9]+(?:\\.)?(?:[0-9]+))\\s*([^-]*)$"); - final Matcher m = dp.matcher(value); + final Matcher m = NUMBER_PATTERN.matcher(this.value); if (!m.matches()) { throw new NumberFormatException(); } final String vDoubleStr = m.group(1); - this.unitStr = m.group(2); - this.vDouble = Double.parseDouble(vDoubleStr); + this.vDouble = Double.parseDouble(vDoubleStr.replaceAll("\\s", "")); + if (vDoubleStr.matches(".*\\s.*")) { + // empty space in scientific notation is a common typo + throw new Query.ParsingException( + "You typed \"" + + vDoubleStr + + "\". Empty spaces are not allowed in numbers. Did you mean \"" + + vDoubleStr.replaceAll("\\s", "") + + "\"?"); + } + this.unitStr = m.group(2); } catch (final NumberFormatException e) { this.vDouble = null; } @@ -153,7 +176,7 @@ public class POV implements EntityFilterInterface { this.unit = getUnit(this.unitStr); } catch (final ParserException e) { e.printStackTrace(); - throw new UnsupportedOperationException("Could not parse the unit."); + throw new UnsupportedOperationException("Could not parse the unit:"); } this.stdUnitSig = this.unit.normalize().getSignature(); @@ -364,8 +387,8 @@ public class POV implements EntityFilterInterface { query.getConnection().prepareCall("call initPOVRefidsTable(?,?)")) { // stmt = this.connection.prepareCall("call initPOV(?,?,?,?,?)"); // initPOVRefidsTable(in vInt INT, in vText VARCHAR(255)) - if (this.vInt != null && this.vInt > 0) { - stmt.setInt(1, this.vInt); + if (this.value != null) { + stmt.setString(1, this.value); } else { stmt.setNull(1, Types.INTEGER); } @@ -382,6 +405,12 @@ public class POV implements EntityFilterInterface { } final long t2 = System.currentTimeMillis(); query.addBenchmark(measurement(".initPOVRefidsTable()"), t2 - t1); + + if (this.refIdsTable != null) { + query.getQuery().filterIntermediateResult(this.refIdsTable); + } + final long t3 = System.currentTimeMillis(); + query.addBenchmark(measurement(".filterRefidsWithoutRetrievePermission"), t3 - t2); try (PreparedStatement stmt = query.getConnection().prepareCall("call initPOVPropertiesTable(?,?,?)")) { // initPOVPropertiesTable(in pid INT UNSIGNED, in pname @@ -432,12 +461,12 @@ public class POV implements EntityFilterInterface { } } } - final long t3 = System.currentTimeMillis(); - query.addBenchmark(measurement(""), t3 - t2); + final long t4 = System.currentTimeMillis(); + query.addBenchmark(measurement(""), t4 - t3); if (this.refIdsTable != null) { query.getQuery().applyQueryTemplates(query, this.refIdsTable); - query.addBenchmark(measurement(".applyQueryTemplates()"), System.currentTimeMillis() - t3); + query.addBenchmark(measurement(".applyQueryTemplates()"), System.currentTimeMillis() - t4); } if (hasSubProperty() && this.targetSet != null) { diff --git a/src/main/java/org/caosdb/server/query/Query.java b/src/main/java/org/caosdb/server/query/Query.java index 64019d3bd3f56b8e2fa83e72adbfc966b2b596ad..8e1b523c0609b4097ec5a7518d5103e9531ee271 100644 --- a/src/main/java/org/caosdb/server/query/Query.java +++ b/src/main/java/org/caosdb/server/query/Query.java @@ -3,8 +3,8 @@ * * Copyright (C) 2018 Research Group Biomedical Physics, * Max-Planck-Institute for Dynamics and Self-Organization Göttingen - * Copyright (C) 2019-2021 IndiScale GmbH <info@indiscale.com> - * Copyright (C) 2019-2021 Timm Fitschen <t.fitschen@indiscale.com> + * Copyright (C) 2019-2023 IndiScale GmbH <info@indiscale.com> + * Copyright (C) 2019-2023 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 @@ -21,7 +21,7 @@ */ package org.caosdb.server.query; -import static org.caosdb.server.database.DatabaseUtils.bytes2UTF8; +import static org.caosdb.server.database.backend.implementation.MySQL.DatabaseUtils.bytes2UTF8; import java.io.Serializable; import java.sql.CallableStatement; @@ -71,13 +71,23 @@ import org.caosdb.server.permissions.EntityACL; import org.caosdb.server.permissions.EntityPermission; import org.caosdb.server.query.CQLParser.CqContext; import org.caosdb.server.query.CQLParsingErrorListener.ParsingError; -import org.caosdb.server.transaction.TransactionInterface; +import org.caosdb.server.transaction.EntityTransactionInterface; +import org.caosdb.server.transaction.Retrieve; +import org.caosdb.server.transaction.Transaction; import org.caosdb.server.transaction.WriteTransaction; -import org.caosdb.server.utils.ResultSetIterator; import org.jdom2.Element; import org.slf4j.Logger; -public class Query implements QueryInterface, ToElementable, TransactionInterface { +/** + * This class represents a single, complete Query execution from the parsing of the query string to + * the resulting list of entity ids. + * + * <p>This class handles caching of queries and checking retrieve permissions as well. It does not, + * however, retrieve the resulting entities; this is handled by the {@link Retrieve} class. + * + * @author Timm Fitschen <t.fitschen@indiscale.com> + */ +public class Query implements QueryInterface, ToElementable, EntityTransactionInterface { /** Class which represents the selection of (sub)properties. */ public static class Selection { @@ -220,22 +230,22 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac /** A data class for storing triplets of (Entity ID, version hash, ACL string) */ public static class IdVersionAclTriplet { - public IdVersionAclTriplet(final Integer id, final String version, final String acl) { + public IdVersionAclTriplet(final String id, final String version, final String acl) { this.id = id; this.version = version; this.acl = acl; } - public Integer id; + public String id; public String version; public String acl; @Override public String toString() { if (version == null) { - return Integer.toString(id); + return id; } - return Integer.toString(id) + "@" + version; + return id + "@" + version; } @Override @@ -258,6 +268,14 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac public int hashCode() { return toString().hashCode(); } + + public boolean isInternal() { + try { + return Integer.parseInt(id) < 100; + } catch (NumberFormatException e) { + return false; + } + } } private final boolean filterEntitiesWithoutRetrievePermisions = @@ -282,6 +300,7 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac private final ArrayList<ToElementable> messages = new ArrayList<>(); private Access access; private boolean versioned = false; + /** * This key-value cache stores lists of of (id, version hash, acl string) triplets. Those values * are the result sets of queries. The keys are created such that they are different for different @@ -296,8 +315,10 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac */ private static ICacheAccess<String, Serializable> cache = Cache.getCache("HIGH_LEVEL_QUERY_CACHE"); + /** Cached=true means that the results of this query have actually been pulled from the cache. */ private boolean cached = false; + /** * Cachable=false means that the results of this query cannot be used for the cache, because the * query evaluation contains complex permission checking which could only be cached on a per-user @@ -305,6 +326,8 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac */ private boolean filteredIntermediateResult = false; + private EntityTransactionInterface transaction; + /** * Tags the query cache and is renewed each time the cache is being cleared, i.e. each time the * database is being updated. @@ -318,18 +341,34 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac } public Query(final String query) { - this(query, null); + this(query, (Subject) null); } public Query(final String query, final Subject user) { - this(query, user, null); + this(query, user, null, null, null); + } + + public Query(final String query, final Transaction<? extends TransactionContainer> transaction) { + this( + query, + transaction.getTransactor(), + transaction.getContainer(), + transaction, + transaction.getAccess()); } - public Query(final String query, final Subject user, final TransactionContainer container) { + public Query( + final String query, + final Subject user, + final TransactionContainer container, + final EntityTransactionInterface transaction, + final Access access) { this.container = container; + this.transaction = transaction; this.query = query; this.user = user; - }; + this.access = access; + } /** * Fill the initially empty resultSet with all entities which match the name, or the id. Then @@ -341,9 +380,9 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac final CallableStatement callInitEntity = getConnection().prepareCall("call initEntity(?,?,?,?,?,?)"); - try { - callInitEntity.setInt(1, Integer.parseInt(this.entity.toString())); - } catch (final NumberFormatException e) { + if (matchIdPattern(this.entity.toString())) { + callInitEntity.setString(1, this.entity.toString()); + } else { callInitEntity.setNull(1, Types.INTEGER); } switch (this.entity.type) { @@ -414,12 +453,19 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac final Map<EntityID, String> queryTemplates = getQueryTemplates(query, resultSet); final PreparedStatement removeQTStmt = - query.getConnection().prepareStatement("DELETE FROM `" + resultSet + "` WHERE id=?"); + query + .getConnection() + .prepareStatement( + "DELETE FROM `" + + resultSet + + "` WHERE EXISTS (SELECT 1 FROM entity_ids AS eids WHERE eids.id=? AND eids.internal_id=`" + + resultSet + + "`.id)"); // Run thru all QTs found... for (final Entry<EntityID, String> q : queryTemplates.entrySet()) { // ... remove the QT from resultSet... - removeQTStmt.setInt(1, q.getKey().toInteger()); + removeQTStmt.setString(1, q.getKey().toString()); removeQTStmt.execute(); // ... check for RETRIEVE:ENTITY permission... @@ -432,8 +478,8 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac } // ... apply them... - final Query subQuery = new Query(q.getValue(), query.getUser()); - subQuery.setAccess(query.getAccess()); + final Query subQuery = + new Query(q.getValue(), query.getUser(), null, transaction, query.getAccess()); subQuery.parse(); // versioning for QueryTemplates is not supported and probably never will. @@ -470,12 +516,12 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac query .getConnection() .prepareCall( - "SELECT q.id, q.definition FROM query_template_def AS q INNER JOIN `" + "SELECT (SELECT eids.id FROM entity_ids AS eids WHERE eids.internal_id = q.id) as id, q.definition FROM query_template_def AS q INNER JOIN `" + resultSet + "` AS r ON (r.id=q.id);"); rs = stmt.executeQuery(); while (rs.next()) { - ret.put(new EntityID(rs.getInt("id")), rs.getString("definition")); + ret.put(new EntityID(rs.getString("id")), rs.getString("definition")); } return ret; } finally { @@ -627,7 +673,7 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac if (this.entity != null) { return sourceStrategy(initQuery(versioned)); } else if (this.role == Role.ENTITY && this.filter == null) { - return "entities"; + return "entity_ids"; } else { return targetStrategy(initQuery(versioned)); } @@ -646,29 +692,27 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac */ private String generateSelectStatementForResultSet( final String resultSetTableName, final boolean versioned) { - // TODO remove the entities.role part when - // https://gitlab.indiscale.com/caosdb/src/caosdb-server/-/issues/245 is resolved - if (resultSetTableName.equals("entities")) { + if (resultSetTableName.equals("entity_ids")) { final String baseStatement = - "SELECT entities.id, entity_acl.acl FROM entities INNER JOIN entity_acl ON entity_acl.id=entities.acl WHERE entities.role!='DOMAIN'"; + "SELECT entities.id AS internal_id, (SELECT id FROM entity_ids WHERE internal_id = entities.id) as id, entity_acl.acl FROM entities INNER JOIN entity_acl ON entity_acl.id=entities.acl"; if (!versioned) { return baseStatement + ";"; } // if versioned, the statement is surrounded with another SELECT and JOIN return ("SELECT id, acl, version FROM (" + baseStatement - + ") AS tmp JOIN entity_version ON entity_version.entity_id=tmp.id;"); + + ") AS tmp JOIN entity_version ON entity_version.entity_id=tmp.internal_id;"); } else { if (!versioned) { - return (" SELECT tmp.id, entity_acl.acl FROM " + return (" SELECT (SELECT id FROM entity_ids WHERE internal_id = tmp.id) AS id, entity_acl.acl FROM " + " (SELECT results.id AS id, entities.acl AS acl_id FROM `" + resultSetTableName - + "` AS results JOIN entities ON results.id=entities.id WHERE entities.role!='DOMAIN') AS tmp" + + "` AS results JOIN entities ON results.id=entities.id) AS tmp" + " JOIN entity_acl ON entity_acl.id=tmp.acl_id") + ";"; } // if versioned, the statement is surrounded with another SELECT and JOIN - return ("SELECT tmp2.id, acl, version FROM( SELECT tmp.id, entity_acl.acl, tmp._iversion AS _iversion FROM " + return ("SELECT (SELECT id FROM entity_ids WHERE internal_id = tmp2.id) AS id, acl, version FROM (SELECT tmp.id, entity_acl.acl, tmp._iversion AS _iversion FROM " + " (SELECT results.id AS id, entities.acl AS acl_id, results._iversion AS _iversion FROM `" + resultSetTableName + "` AS results JOIN entities ON results.id=entities.id) AS tmp" @@ -694,10 +738,14 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac finishResultSet = finish.executeQuery(); final List<IdVersionAclTriplet> rs = new LinkedList<>(); while (finishResultSet.next()) { + final String id = finishResultSet.getString("id"); + if (finishResultSet.wasNull()) { + continue; + } final String version = versioned ? finishResultSet.getString("version") : null; final String acl = finishResultSet.getString("acl"); - rs.add(new IdVersionAclTriplet(finishResultSet.getInt("id"), version, acl)); + rs.add(new IdVersionAclTriplet(id, version, acl)); } return rs; } catch (final SQLException e) { @@ -726,6 +774,7 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac this.resultSet = getCached(getCacheKey(false)); } } + /** Store the content of `resultSet` member in the high level query cache. */ private void storeResultInCache() { // Decide whether user specific cache needs to be used or not @@ -737,6 +786,7 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac cacheItem(getCacheKey(false), this.resultSet); } } + /** Fill entities from `resultSet` into `container`. */ private void fillContainerWithResult() { if (this.container != null && (this.type == Type.FIND || this.type == Type.SELECT)) { @@ -758,11 +808,10 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac * * <p>First try the cache and only then use the back-end. * - * @param access - * @return * @throws ParsingException, QueryException */ - public Query execute(final Access access) throws ParsingException, QueryException { + @Override + public void execute() throws ParsingException, QueryException { try { parse(); setAccess(access); @@ -786,8 +835,8 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac e.printStackTrace(); throw new QueryException(e); } - return this; } + /** Remove all cached queries from the cache. */ public static void clearCache() { cacheETag = UUID.randomUUID().toString(); @@ -800,7 +849,7 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac final List<IdVersionAclTriplet> filtered = new ArrayList<>(); for (final IdVersionAclTriplet triplet : resultSet) { - if (triplet.id >= 100) { + if (!triplet.isInternal()) { filtered.add(triplet); } } @@ -910,11 +959,11 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac + "left join entity_acl on entity_n_acl.acl=entity_acl.id;"); final ResultSet entitiesRS = stmt.executeQuery(query); final ResultSetIterator entitiesWithACL = new ResultSetIterator(entitiesRS); - final List<Integer> toBeDeleted = collectIdsWithoutPermission(entitiesWithACL); + final List<String> toBeDeleted = collectIdsWithoutPermission(entitiesWithACL); try (final PreparedStatement pstmt = this.getConnection().prepareStatement("DELETE FROM `" + tabname + "` WHERE id = ?")) { - for (final Integer id : toBeDeleted) { - pstmt.setInt(1, id); + for (final String id : toBeDeleted) { + pstmt.setString(1, id); pstmt.execute(); } } @@ -933,8 +982,8 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac */ private List<IdVersionAclTriplet> filterEntitiesWithoutRetrievePermission( final List<IdVersionAclTriplet> resultSet) { - final List<Integer> toBeDeleted = collectIdsWithoutPermission(resultSet.iterator()); - final List<IdVersionAclTriplet> filtered = new ArrayList<>(); + final List<String> toBeDeleted = collectIdsWithoutPermission(resultSet.iterator()); + final List<IdVersionAclTriplet> filtered = new LinkedList<>(); for (final IdVersionAclTriplet triplet : resultSet) { if (-1 == toBeDeleted.indexOf(triplet.id)) { filtered.add(triplet); @@ -942,6 +991,7 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac } return filtered; } + /** * Creates a list with IDs of those entities that do not have sufficient RETRIEVE permission * @@ -949,9 +999,9 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac * triplets. * @return compiled list */ - private List<Integer> collectIdsWithoutPermission(Iterator<IdVersionAclTriplet> entityIterator) { - final HashMap<String, Boolean> acl_cache = new HashMap<String, Boolean>(); - final List<Integer> toBeDeleted = new LinkedList<Integer>(); + private List<String> collectIdsWithoutPermission(Iterator<IdVersionAclTriplet> entityIterator) { + final Map<String, Boolean> acl_cache = new HashMap<>(); + final List<String> toBeDeleted = new LinkedList<>(); while (entityIterator.hasNext()) { final long t1 = System.currentTimeMillis(); @@ -1001,7 +1051,9 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac return this.access; } - /** @return the number of entities in the resultset. Might be updated by the filters. */ + /** + * @return the number of entities in the resultset. Might be updated by the filters. + */ @Override public int getTargetSetCount() { return this.targetSetCount; @@ -1085,11 +1137,6 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac return this.targetSet; } - @Override - public void execute() throws Exception { - execute(getAccess()); - } - public List<Selection> getSelections() { return this.selections; } @@ -1183,4 +1230,9 @@ public class Query implements QueryInterface, ToElementable, TransactionInterfac public UTCDateTime getTimestamp() { return null; } + + @Override + public boolean matchIdPattern(String id) { + return transaction.matchIdPattern(id); + } } diff --git a/src/main/java/org/caosdb/server/utils/ResultSetIterator.java b/src/main/java/org/caosdb/server/query/ResultSetIterator.java similarity index 89% rename from src/main/java/org/caosdb/server/utils/ResultSetIterator.java rename to src/main/java/org/caosdb/server/query/ResultSetIterator.java index 0912d278c7158c9b7b75a585c2827d851951f29b..a26c1f41867e96722dd4ba6451344b689d0299e0 100644 --- a/src/main/java/org/caosdb/server/utils/ResultSetIterator.java +++ b/src/main/java/org/caosdb/server/query/ResultSetIterator.java @@ -1,6 +1,6 @@ -package org.caosdb.server.utils; +package org.caosdb.server.query; -import static org.caosdb.server.database.DatabaseUtils.bytes2UTF8; +import static org.caosdb.server.database.backend.implementation.MySQL.DatabaseUtils.bytes2UTF8; import java.sql.ResultSet; import java.sql.SQLException; @@ -35,7 +35,8 @@ public class ResultSetIterator implements Iterator<IdVersionAclTriplet> { this.cursorHasMoved = true; } return this.currentIsValid; - }; + } + ; public IdVersionAclTriplet next() { if (!this.cursorHasMoved) { @@ -50,7 +51,7 @@ public class ResultSetIterator implements Iterator<IdVersionAclTriplet> { throw new NoSuchElementException(); } try { - final Integer id = resultSet.getInt("id"); + final String id = resultSet.getString("id"); final String acl_str = bytes2UTF8(resultSet.getBytes("ACL")); return new IdVersionAclTriplet(id, "", acl_str); } catch (SQLException e) { diff --git a/src/main/java/org/caosdb/server/query/SubProperty.java b/src/main/java/org/caosdb/server/query/SubProperty.java index 8ff167eac1e045875576a75f3d922f5c6461c68e..d5ce923449b45cb45dab77e4c30b5d8b343e01e3 100644 --- a/src/main/java/org/caosdb/server/query/SubProperty.java +++ b/src/main/java/org/caosdb/server/query/SubProperty.java @@ -23,7 +23,7 @@ package org.caosdb.server.query; import static java.sql.Types.VARCHAR; -import static org.caosdb.server.database.DatabaseUtils.bytes2UTF8; +import static org.caosdb.server.database.backend.implementation.MySQL.DatabaseUtils.bytes2UTF8; import java.sql.CallableStatement; import java.sql.Connection; diff --git a/src/main/java/org/caosdb/server/resource/AbstractCaosDBServerResource.java b/src/main/java/org/caosdb/server/resource/AbstractCaosDBServerResource.java index bf5ccce8de2cc6d2d50fe563591c69cca91293c8..6d5cb3bae7311a95347a0ca4baf0ffb04a7ad07e 100644 --- a/src/main/java/org/caosdb/server/resource/AbstractCaosDBServerResource.java +++ b/src/main/java/org/caosdb/server/resource/AbstractCaosDBServerResource.java @@ -244,8 +244,12 @@ public abstract class AbstractCaosDBServerResource extends ServerResource { } protected abstract Representation httpGetInChildClass() - throws ConnectionException, IOException, SQLException, CaosDBException, - NoSuchAlgorithmException, Exception; + throws ConnectionException, + IOException, + SQLException, + CaosDBException, + NoSuchAlgorithmException, + Exception; @Post public Representation httpPost(final Representation entity) { @@ -278,8 +282,12 @@ public abstract class AbstractCaosDBServerResource extends ServerResource { } protected Representation httpDeleteInChildClass() - throws ConnectionException, SQLException, CaosDBException, IOException, - NoSuchAlgorithmException, Exception { + throws ConnectionException, + SQLException, + CaosDBException, + IOException, + NoSuchAlgorithmException, + Exception { getResponse().setStatus(Status.CLIENT_ERROR_METHOD_NOT_ALLOWED); return null; } @@ -294,8 +302,14 @@ public abstract class AbstractCaosDBServerResource extends ServerResource { } protected Representation httpPostInChildClass(final Representation entity) - throws ConnectionException, SQLException, CaosDBException, IOException, - NoSuchAlgorithmException, xmlNotWellFormedException, JDOMException, Exception { + throws ConnectionException, + SQLException, + CaosDBException, + IOException, + NoSuchAlgorithmException, + xmlNotWellFormedException, + JDOMException, + Exception { getResponse().setStatus(Status.CLIENT_ERROR_METHOD_NOT_ALLOWED); return null; } diff --git a/src/main/java/org/caosdb/server/resource/AuthenticationResource.java b/src/main/java/org/caosdb/server/resource/AuthenticationResource.java index ed60d7906b1cda003ed2a84f0e22200049d96c25..4f53ba12e958a33d694357372afba274fcfe4151 100644 --- a/src/main/java/org/caosdb/server/resource/AuthenticationResource.java +++ b/src/main/java/org/caosdb/server/resource/AuthenticationResource.java @@ -52,8 +52,12 @@ public class AuthenticationResource extends AbstractCaosDBServerResource { /** Returns single "Login" element. XSLT will create a form. */ @Override protected Representation httpGetInChildClass() - throws ConnectionException, IOException, SQLException, CaosDBException, - NoSuchAlgorithmException, Exception { + throws ConnectionException, + IOException, + SQLException, + CaosDBException, + NoSuchAlgorithmException, + Exception { final Document doc = new Document(); final Element rootElem = generateRootElement(); doc.setRootElement(rootElem); @@ -66,8 +70,12 @@ public class AuthenticationResource extends AbstractCaosDBServerResource { @Override protected Representation httpDeleteInChildClass() - throws ConnectionException, SQLException, CaosDBException, IOException, - NoSuchAlgorithmException, Exception { + throws ConnectionException, + SQLException, + CaosDBException, + IOException, + NoSuchAlgorithmException, + Exception { final Subject user = SecurityUtils.getSubject(); if (user.isAuthenticated()) { user.logout(); diff --git a/src/main/java/org/caosdb/server/resource/DefaultResource.java b/src/main/java/org/caosdb/server/resource/DefaultResource.java index 488cd667c8832e4b90277e07144c65cbb7e4036d..39fbf47dcd4cde1d96642a0112b58c3285dec2c9 100644 --- a/src/main/java/org/caosdb/server/resource/DefaultResource.java +++ b/src/main/java/org/caosdb/server/resource/DefaultResource.java @@ -43,8 +43,12 @@ public class DefaultResource extends AbstractCaosDBServerResource { @Override protected Representation httpGetInChildClass() - throws ConnectionException, IOException, SQLException, CaosDBException, - NoSuchAlgorithmException, Exception { + throws ConnectionException, + IOException, + SQLException, + CaosDBException, + NoSuchAlgorithmException, + Exception { final Document doc = new org.jdom2.Document(); final Element root = generateRootElement(); if (this.responseBody != null) { diff --git a/src/main/java/org/caosdb/server/resource/FileSystemResource.java b/src/main/java/org/caosdb/server/resource/FileSystemResource.java index 407d6aac0ff86c3c38030dd7ceb485d314e0c5cb..e6859caac82ee685e2938d7c9dc2a121ceeb6e26 100644 --- a/src/main/java/org/caosdb/server/resource/FileSystemResource.java +++ b/src/main/java/org/caosdb/server/resource/FileSystemResource.java @@ -34,6 +34,7 @@ import org.caosdb.server.entity.EntityID; import org.caosdb.server.entity.EntityInterface; import org.caosdb.server.entity.Message; import org.caosdb.server.entity.Message.MessageType; +import org.caosdb.server.entity.RetrieveEntity; import org.caosdb.server.entity.container.RetrieveContainer; import org.caosdb.server.filesystem.FileSystem; import org.caosdb.server.filesystem.FileSystem.ObjectType; @@ -91,14 +92,14 @@ public class FileSystemResource extends AbstractCaosDBServerResource { file = entity.getFSODescriptor(); if (file.getObjectType() == ObjectType.DIRECTORY) { - String referenceString = getReferenceString(file); - if (!referenceString.endsWith(Path.DEFAULT_PATH_SEPARATOR)) { - referenceString = referenceString + Path.DEFAULT_PATH_SEPARATOR; + String url = getUtils().getServerRootURI() + "/FileSystem/" + file.getPath().toString(); + if (!url.endsWith(Path.DEFAULT_PATH_SEPARATOR)) { + url = url + Path.DEFAULT_PATH_SEPARATOR; } final Element folder = new Element("dir"); folder.setAttribute("path", Path.DEFAULT_PATH_SEPARATOR + file.getPath().toString()); - folder.setAttribute("name", path.isEmpty() ? "/" : file.getName()); - folder.setAttribute("url", referenceString); + folder.setAttribute("name", file.getPath().isEmpty() ? "/" : file.getName()); + folder.setAttribute("url", url); for (final VirtualFSODescriptorInterface child : file.listChildren()) { Element celem = null; @@ -106,24 +107,23 @@ public class FileSystemResource extends AbstractCaosDBServerResource { case DIRECTORY: celem = new Element("dir"); celem.setAttribute("name", child.getName()); - celem.setAttribute( - "url", referenceString + child.getName() + Path.DEFAULT_PATH_SEPARATOR); + celem.setAttribute("url", url + child.getName() + Path.DEFAULT_PATH_SEPARATOR); break; case LINK: celem = new Element("link"); celem.setAttribute("name", child.getName()); - celem.setAttribute("url", referenceString + child.getName()); + celem.setAttribute("url", url + child.getName()); break; case FILE: celem = new Element("file"); celem.setAttribute("name", child.getName()); - celem.setAttribute("url", referenceString + child.getName()); + celem.setAttribute("url", url + child.getName()); break; default: throw new UnsupportedOperationException( "Unknown FileSystem.ObjectType: " + child.getObjectType().toString()); } - if (child.getEntityId().toInteger() != null) { + if (child.getEntityId().toString() != null) { celem.setAttribute("id", child.getEntityId().toString()); } @@ -189,7 +189,7 @@ public class FileSystemResource extends AbstractCaosDBServerResource { Map<String, String> flags = getFlags(); flags.put("listDirectory", "true"); RetrieveContainer c = new RetrieveContainer(getUser(), getTimestamp(), getSRID(), flags); - c.add(id); + c.add(new RetrieveEntity(id)); Retrieve t = new Retrieve(c); t.execute(); final long t2 = System.currentTimeMillis(); diff --git a/src/main/java/org/caosdb/server/resource/LogoutResource.java b/src/main/java/org/caosdb/server/resource/LogoutResource.java index 1659f751a4710fbf99fc473b586cc3b180c8a62c..f17bd0c65119bc21183a0d7165a62688e60f2d25 100644 --- a/src/main/java/org/caosdb/server/resource/LogoutResource.java +++ b/src/main/java/org/caosdb/server/resource/LogoutResource.java @@ -33,8 +33,12 @@ public class LogoutResource extends AuthenticationResource { @Override protected Representation httpGetInChildClass() - throws ConnectionException, IOException, SQLException, CaosDBException, - NoSuchAlgorithmException, Exception { + throws ConnectionException, + IOException, + SQLException, + CaosDBException, + NoSuchAlgorithmException, + Exception { return super.httpDeleteInChildClass(); } } diff --git a/src/main/java/org/caosdb/server/resource/PermissionRulesResource.java b/src/main/java/org/caosdb/server/resource/PermissionRulesResource.java index 8d2aa1720aaefcdbaccee18c6a1626a912319b6a..afdcb0d60f077ebcfc2dfcc0e99093289a6d621f 100644 --- a/src/main/java/org/caosdb/server/resource/PermissionRulesResource.java +++ b/src/main/java/org/caosdb/server/resource/PermissionRulesResource.java @@ -44,8 +44,12 @@ public class PermissionRulesResource extends AbstractCaosDBServerResource { @Override protected Representation httpGetInChildClass() - throws ConnectionException, IOException, SQLException, CaosDBException, - NoSuchAlgorithmException, Exception { + throws ConnectionException, + IOException, + SQLException, + CaosDBException, + NoSuchAlgorithmException, + Exception { final String role = getRequestedItems()[0]; getUser().checkPermission(ACMPermissions.PERMISSION_RETRIEVE_ROLE_PERMISSIONS(role)); diff --git a/src/main/java/org/caosdb/server/resource/RolesResource.java b/src/main/java/org/caosdb/server/resource/RolesResource.java index ad1544da3a326f1c16e0b01238985d10e93b0581..73dee43e1f6b49d2c73cf44fe80510bb704d4f0d 100644 --- a/src/main/java/org/caosdb/server/resource/RolesResource.java +++ b/src/main/java/org/caosdb/server/resource/RolesResource.java @@ -46,8 +46,12 @@ public class RolesResource extends AbstractCaosDBServerResource { @Override protected Representation httpGetInChildClass() - throws ConnectionException, IOException, SQLException, CaosDBException, - NoSuchAlgorithmException, Exception { + throws ConnectionException, + IOException, + SQLException, + CaosDBException, + NoSuchAlgorithmException, + Exception { final Element root = generateRootElement(); final Document document = new Document(); @@ -74,8 +78,12 @@ public class RolesResource extends AbstractCaosDBServerResource { @Override protected Representation httpDeleteInChildClass() - throws ConnectionException, SQLException, CaosDBException, IOException, - NoSuchAlgorithmException, Exception { + throws ConnectionException, + SQLException, + CaosDBException, + IOException, + NoSuchAlgorithmException, + Exception { if (getRequestedItems().length > 0) { final String name = getRequestedItems()[0]; if (name != null) { @@ -97,8 +105,14 @@ public class RolesResource extends AbstractCaosDBServerResource { @Override protected Representation httpPostInChildClass(final Representation entity) - throws ConnectionException, SQLException, CaosDBException, IOException, - NoSuchAlgorithmException, xmlNotWellFormedException, JDOMException, Exception { + throws ConnectionException, + SQLException, + CaosDBException, + IOException, + NoSuchAlgorithmException, + xmlNotWellFormedException, + JDOMException, + Exception { String name = null; String description = null; diff --git a/src/main/java/org/caosdb/server/resource/ServerLogsResource.java b/src/main/java/org/caosdb/server/resource/ServerLogsResource.java deleted file mode 100644 index f304f10062209d62855a0bff21e0c8cad54e1962..0000000000000000000000000000000000000000 --- a/src/main/java/org/caosdb/server/resource/ServerLogsResource.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * ** header v3.0 - * This file is a part of the CaosDB Project. - * - * Copyright (C) 2018 Research Group Biomedical Physics, - * Max-Planck-Institute for Dynamics and Self-Organization Göttingen - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <https://www.gnu.org/licenses/>. - * - * ** end header - */ -package org.caosdb.server.resource; - -import java.io.IOException; -import java.security.NoSuchAlgorithmException; -import java.sql.SQLException; -import java.util.List; -import java.util.logging.Level; -import java.util.logging.LogRecord; -import java.util.logging.XMLFormatter; -import org.caosdb.server.CaosDBException; -import org.caosdb.server.database.backend.implementation.MySQL.ConnectionException; -import org.caosdb.server.transaction.RetrieveLogRecordTransaction; -import org.restlet.data.Form; -import org.restlet.data.MediaType; -import org.restlet.representation.Representation; -import org.restlet.representation.StringRepresentation; - -public class ServerLogsResource extends AbstractCaosDBServerResource { - - @Override - protected Representation httpGetInChildClass() - throws ConnectionException, IOException, SQLException, CaosDBException, - NoSuchAlgorithmException, Exception { - - Level level = null; - String message = null; - final String logger = - getRequest().getResourceRef().getRemainingPart(true, false).replaceAll("/", "."); - final Form form = getRequest().getResourceRef().getQueryAsForm(true); - if (form != null) { - final String levelStr = form.getFirstValue("level"); - if (levelStr != null) { - try { - level = Level.parse(levelStr); - } catch (final IllegalArgumentException e) { - level = Level.OFF; - } - } - message = form.getFirstValue("message"); - } - - final RetrieveLogRecordTransaction t = new RetrieveLogRecordTransaction(logger, level, message); - t.execute(); - - generateRootElement(); - final List<LogRecord> logRecords = t.getLogRecords(); - - final XMLFormatter xmlFormatter = new XMLFormatter(); - String ret = xmlFormatter.getHead(null); - for (final LogRecord r : logRecords) { - ret += xmlFormatter.format(r); - } - ret += xmlFormatter.getTail(null); - - return new StringRepresentation(ret, MediaType.TEXT_XML); - } -} diff --git a/src/main/java/org/caosdb/server/resource/UserResource.java b/src/main/java/org/caosdb/server/resource/UserResource.java index de07331e9637549285d71ca2fd910916cd13316a..ea8e8a264542474cccf74d20cf070342cb43d717 100644 --- a/src/main/java/org/caosdb/server/resource/UserResource.java +++ b/src/main/java/org/caosdb/server/resource/UserResource.java @@ -1,9 +1,10 @@ /* - * ** header v3.0 * This file is a part of the CaosDB Project. * * Copyright (C) 2018 Research Group Biomedical Physics, * Max-Planck-Institute for Dynamics and Self-Organization Göttingen + * Copyright (C) 2023 IndiScale GmbH <info@indiscale.com> + * Copyright (C) 2023 Timm Fitschen <t.fitschen@indiscale.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -17,8 +18,6 @@ * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. - * - * ** end header */ package org.caosdb.server.resource; @@ -48,14 +47,18 @@ import org.restlet.representation.Representation; /** * This class handles requests for Users. * - * @author Timm Fitschen + * @author Timm Fitschen <t.fitschen@indiscale.com> */ public class UserResource extends AbstractCaosDBServerResource { @Override protected Representation httpGetInChildClass() - throws ConnectionException, IOException, SQLException, CaosDBException, - NoSuchAlgorithmException, Exception { + throws ConnectionException, + IOException, + SQLException, + CaosDBException, + NoSuchAlgorithmException, + Exception { final Document doc = new Document(); final Element rootElem = generateRootElement(); @@ -104,12 +107,12 @@ public class UserResource extends AbstractCaosDBServerResource { form.getFirstValue("status") != null ? UserStatus.valueOf(form.getFirstValue("status").toUpperCase()) : null; - Integer userEntity = null; + String userEntity = null; if (form.getFirst("entity") != null) { if (form.getFirstValue("entity").isEmpty()) { - userEntity = 0; + userEntity = ""; } else { - userEntity = Integer.parseInt(form.getFirstValue("entity")); + userEntity = form.getFirstValue("entity"); } } @@ -150,9 +153,9 @@ public class UserResource extends AbstractCaosDBServerResource { "status", CaosDBServer.getServerProperty(ServerProperties.KEY_NEW_USER_DEFAULT_ACTIVITY)) .toUpperCase()); - Integer userEntity = null; + String userEntity = null; if (form.getFirst("entity") != null) { - userEntity = Integer.parseInt(form.getFirstValue("entity")); + userEntity = form.getFirstValue("entity"); } final InsertUserTransaction t = @@ -177,8 +180,12 @@ public class UserResource extends AbstractCaosDBServerResource { @Override protected Representation httpDeleteInChildClass() - throws ConnectionException, SQLException, CaosDBException, IOException, - NoSuchAlgorithmException, Exception { + throws ConnectionException, + SQLException, + CaosDBException, + IOException, + NoSuchAlgorithmException, + Exception { final Document doc = new Document(); final Element rootElem = generateRootElement(); diff --git a/src/main/java/org/caosdb/server/resource/UserRolesResource.java b/src/main/java/org/caosdb/server/resource/UserRolesResource.java index 4eb7f6143038bb6e4b0c3fda4646e2730acfea04..72fc906a54b753166a4f5358dfda31ba0dff953e 100644 --- a/src/main/java/org/caosdb/server/resource/UserRolesResource.java +++ b/src/main/java/org/caosdb/server/resource/UserRolesResource.java @@ -45,8 +45,12 @@ public class UserRolesResource extends AbstractCaosDBServerResource { @Override protected Representation httpGetInChildClass() - throws ConnectionException, IOException, SQLException, CaosDBException, - NoSuchAlgorithmException, Exception { + throws ConnectionException, + IOException, + SQLException, + CaosDBException, + NoSuchAlgorithmException, + Exception { final String user = getRequestedItems()[0]; final String realm = (getRequestAttributes().get("realm") != null diff --git a/src/main/java/org/caosdb/server/resource/transaction/EntityResource.java b/src/main/java/org/caosdb/server/resource/transaction/EntityResource.java index b74dd89e14b839c79076efeb7f69b06083bb4c25..dacf548bef8a670d8b1412a32c029532c139565a 100644 --- a/src/main/java/org/caosdb/server/resource/transaction/EntityResource.java +++ b/src/main/java/org/caosdb/server/resource/transaction/EntityResource.java @@ -3,8 +3,8 @@ * * Copyright (C) 2018 Research Group Biomedical Physics, * Max-Planck-Institute for Dynamics and Self-Organization Göttingen - * Copyright (C) 2021 Timm Fitschen <t.fitschen@indiscale.com> - * Copyright (C) 2021 IndiScale GmbH <info@indiscale.com> + * Copyright (C) 2021,2023 Timm Fitschen <t.fitschen@indiscale.com> + * Copyright (C) 2021,2023 IndiScale GmbH <info@indiscale.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -53,11 +53,16 @@ import org.restlet.representation.Representation; * * <p>The GET requests (Retrieval) is handled in the superclass {@link RetrieveEntityResource}. * - * @author Timm Fitschen (t.fitschen@indiscale.com) + * @author Timm Fitschen <t.fitschen@indiscale.com> */ public class EntityResource extends RetrieveEntityResource { - /** Handle entity deletions (DELETE requests). */ + /** + * Handle entity deletions (DELETE requests). + * + * <p>Note: The list of entity "specifier" is treated strictly as a list of entity ids. You cannot + * delete an entity by name. + */ @Override protected final Representation httpDeleteInChildClass() throws Exception { @@ -67,18 +72,14 @@ public class EntityResource extends RetrieveEntityResource { for (final String item : getRequestedItems()) { final String[] elem = item.split("@", 1); - Integer id = null; + String id = null; String version = null; - try { - id = Integer.parseInt(elem[0]); - } catch (final NumberFormatException e) { - // pass - } + id = elem[0]; if (elem.length > 1) { version = elem[1]; } - if (id != null && id > 0) { + if (id != null) { container.add(new DeleteEntity(new EntityID(id), version)); } } @@ -129,8 +130,12 @@ public class EntityResource extends RetrieveEntityResource { * requests which also contain file blobs. */ private Element parseMultipartEntity(final WritableContainer container) - throws FileUploadException, IOException, Message, xmlNotWellFormedException, - NoSuchAlgorithmException, CaosDBException { + throws FileUploadException, + IOException, + Message, + xmlNotWellFormedException, + NoSuchAlgorithmException, + CaosDBException { final DiskFileItemFactory factory = new DiskFileItemFactory(); factory.setSizeThreshold(1000240); final RestletFileUpload upload = new RestletFileUpload(factory); diff --git a/src/main/java/org/caosdb/server/resource/transaction/RetrieveEntityResource.java b/src/main/java/org/caosdb/server/resource/transaction/RetrieveEntityResource.java index 6527376d71ba31718394f887b7335615d80db5c6..d5feea9a0fe815004230e697ff0fa38b74fc72b8 100644 --- a/src/main/java/org/caosdb/server/resource/transaction/RetrieveEntityResource.java +++ b/src/main/java/org/caosdb/server/resource/transaction/RetrieveEntityResource.java @@ -3,8 +3,8 @@ * * Copyright (C) 2018 Research Group Biomedical Physics, * Max-Planck-Institute for Dynamics and Self-Organization Göttingen - * Copyright (C) 2021 Timm Fitschen <t.fitschen@indiscale.com> - * Copyright (C) 2021 IndiScale GmbH <info@indiscale.com> + * Copyright (C) 2021,2023 Timm Fitschen <t.fitschen@indiscale.com> + * Copyright (C) 2021,2023 IndiScale GmbH <info@indiscale.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -26,8 +26,8 @@ import java.security.NoSuchAlgorithmException; import java.sql.SQLException; import org.caosdb.server.CaosDBException; import org.caosdb.server.database.backend.implementation.MySQL.ConnectionException; -import org.caosdb.server.entity.EntityID; import org.caosdb.server.entity.container.RetrieveContainer; +import org.caosdb.server.jobs.core.ResolveNames; import org.caosdb.server.resource.AbstractCaosDBServerResource; import org.caosdb.server.transaction.Retrieve; import org.jdom2.Document; @@ -38,44 +38,40 @@ import org.restlet.representation.Representation; * Handles GET requests for different subclasses which all have in common that they retrieve * Entities (plus other information in some cases). * - * @author Timm Fitschen (t.fitschen@indiscale.com) + * @author Timm Fitschen <t.fitschen@indiscale.com> */ public abstract class RetrieveEntityResource extends AbstractCaosDBServerResource { /** - * Parse the segment which specifies the entities which are to be retrieved + * Parse the segment which specifies the entities which are to be retrieved. * - * @param container + * <p>The segments are being treated as names here. The {@link ResolveNames} job is responsible + * for detecting whether the name is actually an id. */ protected void handleRetrieveContainer(final RetrieveContainer container) { for (final String item : getRequestedItems()) { final String[] elem = item.split("@", 2); - Integer id = null; - String name = null; + String id = null; String version = null; - try { - id = Integer.parseInt(elem[0]); - } catch (final NumberFormatException e) { - name = elem[0]; - } + id = elem[0]; if (elem.length > 1) { version = elem[1]; } - if (id != null) { - container.add(new EntityID(id), version); - } else { - container.add(name); - } + container.add(null, id, version); } } /** Handle the GET request. */ @Override protected final Representation httpGetInChildClass() - throws ConnectionException, IOException, SQLException, CaosDBException, - NoSuchAlgorithmException, Exception { + throws ConnectionException, + IOException, + SQLException, + CaosDBException, + NoSuchAlgorithmException, + Exception { final RetrieveContainer entityContainer = new RetrieveContainer(getUser(), getTimestamp(), getSRID(), getFlags()); diff --git a/src/main/java/org/caosdb/server/scripting/ServerSideScriptingCaller.java b/src/main/java/org/caosdb/server/scripting/ServerSideScriptingCaller.java index 2f12ee455b36049b56787ccc453c7880b080af4b..16f0fcb9df75ea9f9ddb24ba75532946d5449fb5 100644 --- a/src/main/java/org/caosdb/server/scripting/ServerSideScriptingCaller.java +++ b/src/main/java/org/caosdb/server/scripting/ServerSideScriptingCaller.java @@ -270,7 +270,9 @@ public class ServerSideScriptingCaller { } } - /** TODO Should be injected into environment instead. Will be changed in v0.4 of SSS-API */ + /** + * @fixme Should be injected into environment instead. Will be changed in v0.4 of SSS-API + */ String[] injectAuthToken(final String[] commandLine) { final String[] newCommandLine = new String[commandLine.length + 1]; newCommandLine[0] = commandLine[0]; diff --git a/src/main/java/org/caosdb/server/database/backend/interfaces/RegisterSubDomainImpl.java b/src/main/java/org/caosdb/server/transaction/EntityTransactionInterface.java similarity index 56% rename from src/main/java/org/caosdb/server/database/backend/interfaces/RegisterSubDomainImpl.java rename to src/main/java/org/caosdb/server/transaction/EntityTransactionInterface.java index 87ea0c85db6a698f49c103506b520342deff2fb0..9d96c4e7499728ca11c348ac1875d94ece100a72 100644 --- a/src/main/java/org/caosdb/server/database/backend/interfaces/RegisterSubDomainImpl.java +++ b/src/main/java/org/caosdb/server/transaction/EntityTransactionInterface.java @@ -1,9 +1,8 @@ /* - * ** header v3.0 * This file is a part of the CaosDB Project. * - * Copyright (C) 2018 Research Group Biomedical Physics, - * Max-Planck-Institute for Dynamics and Self-Organization Göttingen + * Copyright (C) 2023 IndiScale GmbH <info@indiscale.com> + * Copyright (C) 2023 Timm Fitschen <t.fitschen@indiscale.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -17,16 +16,22 @@ * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. - * - * ** end header */ -package org.caosdb.server.database.backend.interfaces; +package org.caosdb.server.transaction; -import java.util.Deque; -import org.caosdb.server.database.exceptions.TransactionException; import org.caosdb.server.entity.EntityID; -public interface RegisterSubDomainImpl extends BackendTransactionImpl { +/** + * This interface is implemented by all transactions of entities (as opposed to transaction which + * only concern users or other kinds of transactions). + * + * @author Timm Fitschen <t.fitschen@indiscale.com> + */ +public interface EntityTransactionInterface extends TransactionInterface { + + public boolean matchIdPattern(String id); - public abstract Deque<EntityID> execute(int domainCount) throws TransactionException; + public default boolean matchIdPattern(EntityID id) { + return id != null && matchIdPattern(id.toString()); + } } diff --git a/src/main/java/org/caosdb/server/transaction/InsertLogRecordTransaction.java b/src/main/java/org/caosdb/server/transaction/InsertLogRecordTransaction.java deleted file mode 100644 index 950d9b8d25688e47833b60c01335f8dd525a0630..0000000000000000000000000000000000000000 --- a/src/main/java/org/caosdb/server/transaction/InsertLogRecordTransaction.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * ** header v3.0 - * This file is a part of the CaosDB Project. - * - * Copyright (C) 2018 Research Group Biomedical Physics, - * Max-Planck-Institute for Dynamics and Self-Organization Göttingen - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <https://www.gnu.org/licenses/>. - * - * ** end header - */ -package org.caosdb.server.transaction; - -import java.util.List; -import java.util.logging.LogRecord; -import org.caosdb.datetime.UTCDateTime; -import org.caosdb.server.database.DatabaseAccessManager; -import org.caosdb.server.database.access.Access; -import org.caosdb.server.database.backend.transaction.InsertLogRecord; - -public class InsertLogRecordTransaction implements TransactionInterface { - - private final List<LogRecord> toBeFlushed; - private UTCDateTime timestamp; - - public InsertLogRecordTransaction(final List<LogRecord> toBeFlushed) { - this.timestamp = UTCDateTime.SystemMillisToUTCDateTime(System.currentTimeMillis()); - this.toBeFlushed = toBeFlushed; - } - - @Override - public void execute() throws Exception { - final Access access = DatabaseAccessManager.getInstance().acquireReadAccess(this); - try { - execute(new InsertLogRecord(this.toBeFlushed), access); - } finally { - access.release(); - } - } - - @Override - public UTCDateTime getTimestamp() { - return timestamp; - } -} diff --git a/src/main/java/org/caosdb/server/transaction/InsertUserTransaction.java b/src/main/java/org/caosdb/server/transaction/InsertUserTransaction.java index f3f4c7f5fd583ff0ff16d4fd1ce583394258f35b..3174dabf4e3d6a581f27aaf59cb8ffd866401bf5 100644 --- a/src/main/java/org/caosdb/server/transaction/InsertUserTransaction.java +++ b/src/main/java/org/caosdb/server/transaction/InsertUserTransaction.java @@ -50,7 +50,7 @@ public class InsertUserTransaction extends AccessControlTransaction { final String password, final String email, final UserStatus status, - final Integer entity) { + final String entity) { this(new ProtoUser(), password); this.user.realm = UserSources.getInternalRealm().getName(); this.user.name = username; diff --git a/src/main/java/org/caosdb/server/transaction/ListRolesTransaction.java b/src/main/java/org/caosdb/server/transaction/ListRolesTransaction.java index ed3cd3cc5a154e82946e76f9934ee8608fe6980c..f0b0db78eeff6c04ca466dbd6f65f8350fbab783 100644 --- a/src/main/java/org/caosdb/server/transaction/ListRolesTransaction.java +++ b/src/main/java/org/caosdb/server/transaction/ListRolesTransaction.java @@ -39,9 +39,7 @@ public class ListRolesTransaction extends AccessControlTransaction { protected void transaction() throws Exception { Subject currentUser = SecurityUtils.getSubject(); roles = - execute(new ListRoles(), getAccess()) - .getRoles() - .stream() + execute(new ListRoles(), getAccess()).getRoles().stream() .filter( role -> currentUser.isPermitted( diff --git a/src/main/java/org/caosdb/server/transaction/ListUsersTransaction.java b/src/main/java/org/caosdb/server/transaction/ListUsersTransaction.java index 2ade4e8595f159d1fc6996c1e04913f4195ecd97..fe9b9c172d2c6b4361790d30590650ae0233a2ad 100644 --- a/src/main/java/org/caosdb/server/transaction/ListUsersTransaction.java +++ b/src/main/java/org/caosdb/server/transaction/ListUsersTransaction.java @@ -37,9 +37,7 @@ public class ListUsersTransaction extends AccessControlTransaction { protected void transaction() throws Exception { Subject currentUser = SecurityUtils.getSubject(); users = - execute(new ListUsers(), getAccess()) - .getUsers() - .stream() + execute(new ListUsers(), getAccess()).getUsers().stream() .filter( user -> currentUser.isPermitted( diff --git a/src/main/java/org/caosdb/server/transaction/Retrieve.java b/src/main/java/org/caosdb/server/transaction/Retrieve.java index 86b7672cf9ebc0ad8e74f3974ee89174532d9f73..54500c5932d7d8e4af41b5d88f79a2f5f914492f 100644 --- a/src/main/java/org/caosdb/server/transaction/Retrieve.java +++ b/src/main/java/org/caosdb/server/transaction/Retrieve.java @@ -114,11 +114,35 @@ public class Retrieve extends Transaction<RetrieveContainer> { private void retrieveFullEntities(final RetrieveContainer container, final Access access) throws Exception { - execute(new RetrieveFullEntityTransaction(container), access); + execute(new RetrieveFullEntityTransaction(container, getTransactor()), access); } @Override public boolean logHistory() { return false; } + + public static class Paging { + public Paging(int startIndex, int endIndex) { + this.startIndex = startIndex; + this.endIndex = endIndex; + } + + public final int startIndex; + public final int endIndex; + } + + private Retrieve.Paging paging = null; + + public boolean hasPaging() { + return this.paging != null; + } + + public void setPaging(int startIndex, int endIndex) { + this.paging = new Retrieve.Paging(startIndex, endIndex); + } + + public Retrieve.Paging getPaging() { + return paging; + } } diff --git a/src/main/java/org/caosdb/server/transaction/RetrieveACL.java b/src/main/java/org/caosdb/server/transaction/RetrieveACL.java index ac0a2896451a43ca10985bd38787ad4f847a05cc..b5a4a489c60269dbae906bba1942f8e9525eb710 100644 --- a/src/main/java/org/caosdb/server/transaction/RetrieveACL.java +++ b/src/main/java/org/caosdb/server/transaction/RetrieveACL.java @@ -40,7 +40,7 @@ public class RetrieveACL extends Transaction<TransactionContainer> { new TransactionContainer( SecurityUtils.getSubject(), System.currentTimeMillis(), UUID.randomUUID().toString())); for (String strId : idList) { - getContainer().add(new RetrieveEntity(new EntityID(Integer.parseInt(strId)))); + getContainer().add(new RetrieveEntity(new EntityID(strId))); } } diff --git a/src/main/java/org/caosdb/server/transaction/RetrieveLogRecordTransaction.java b/src/main/java/org/caosdb/server/transaction/RetrieveLogRecordTransaction.java deleted file mode 100644 index 2f6cafd6e4e55ab868f69f1d3414a2196f0a4fea..0000000000000000000000000000000000000000 --- a/src/main/java/org/caosdb/server/transaction/RetrieveLogRecordTransaction.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * ** header v3.0 - * This file is a part of the CaosDB Project. - * - * Copyright (C) 2018 Research Group Biomedical Physics, - * Max-Planck-Institute for Dynamics and Self-Organization Göttingen - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <https://www.gnu.org/licenses/>. - * - * ** end header - */ -package org.caosdb.server.transaction; - -import java.util.List; -import java.util.logging.Level; -import java.util.logging.LogRecord; -import org.apache.shiro.SecurityUtils; -import org.caosdb.datetime.UTCDateTime; -import org.caosdb.server.accessControl.ACMPermissions; -import org.caosdb.server.database.DatabaseAccessManager; -import org.caosdb.server.database.access.Access; -import org.caosdb.server.database.backend.transaction.RetrieveLogRecord; - -public class RetrieveLogRecordTransaction implements TransactionInterface { - - private List<LogRecord> logRecords; - private final String logger; - private final Level level; - private final String message; - private UTCDateTime timestamp; - - public RetrieveLogRecordTransaction( - final String logger, final Level level, final String message) { - this.level = level; - this.timestamp = UTCDateTime.SystemMillisToUTCDateTime(System.currentTimeMillis()); - if (message != null && message.isEmpty()) { - this.message = null; - } else if (message != null) { - this.message = message.replaceAll("\\*", "%"); - } else { - this.message = null; - } - if (logger != null && logger.isEmpty()) { - this.logger = null; - } else if (logger != null) { - this.logger = logger.replaceAll("\\*", "%"); - } else { - this.logger = logger; - } - } - - @Override - public void execute() throws Exception { - SecurityUtils.getSubject().checkPermission(ACMPermissions.PERMISSION_RETRIEVE_SERVERLOGS); - final Access access = DatabaseAccessManager.getInstance().acquireReadAccess(this); - try { - this.logRecords = - execute(new RetrieveLogRecord(this.logger, this.level, this.message), access) - .getLogRecords(); - } finally { - access.release(); - } - } - - public List<LogRecord> getLogRecords() { - return this.logRecords; - } - - @Override - public UTCDateTime getTimestamp() { - return timestamp; - } -} diff --git a/src/main/java/org/caosdb/server/transaction/RetrieveUserTransaction.java b/src/main/java/org/caosdb/server/transaction/RetrieveUserTransaction.java index eb24fb24db4f1b6a50a279cfcd9e158003a70948..72a1d61929e692e551a53013ac1aabb1a4f28f5a 100644 --- a/src/main/java/org/caosdb/server/transaction/RetrieveUserTransaction.java +++ b/src/main/java/org/caosdb/server/transaction/RetrieveUserTransaction.java @@ -77,7 +77,7 @@ public class RetrieveUserTransaction extends AccessControlTransaction { ret.setAttribute("email", user.email); } if (user.entity != null) { - ret.setAttribute("entity", Integer.toString(user.entity)); + ret.setAttribute("entity", user.entity); } if (user.status != null) { ret.setAttribute("status", user.status.toString()); diff --git a/src/main/java/org/caosdb/server/transaction/Transaction.java b/src/main/java/org/caosdb/server/transaction/Transaction.java index 1d439178ce9f524b274ecc7efd6dfbbf325d0481..bd73da6bcbd4a20abdb19b28b575f12abce11e6c 100644 --- a/src/main/java/org/caosdb/server/transaction/Transaction.java +++ b/src/main/java/org/caosdb/server/transaction/Transaction.java @@ -3,8 +3,8 @@ * * Copyright (C) 2018 Research Group Biomedical Physics, * Max-Planck-Institute for Dynamics and Self-Organization Göttingen - * Copyright (C) 2019-2021 IndiScale GmbH <info@indiscale.com> - * Copyright (C) 2019-2021 Timm Fitschen <t.fitschen@indiscale.com> + * Copyright (C) 2019-2021,2023 IndiScale GmbH <info@indiscale.com> + * Copyright (C) 2019-2021,2023 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 @@ -21,8 +21,12 @@ */ package org.caosdb.server.transaction; +import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.Set; import org.apache.shiro.subject.Subject; import org.caosdb.datetime.UTCDateTime; import org.caosdb.server.accessControl.Principal; @@ -31,6 +35,7 @@ import org.caosdb.server.database.access.Access; import org.caosdb.server.database.backend.transaction.InsertTransactionHistory; import org.caosdb.server.database.exceptions.TransactionException; import org.caosdb.server.database.misc.TransactionBenchmark; +import org.caosdb.server.entity.EntityIdRegistry; import org.caosdb.server.entity.EntityInterface; import org.caosdb.server.entity.Message; import org.caosdb.server.entity.container.TransactionContainer; @@ -46,11 +51,91 @@ import org.caosdb.server.permissions.EntityACL; import org.caosdb.server.utils.AbstractObservable; import org.caosdb.server.utils.Info; import org.caosdb.server.utils.Observer; +import org.caosdb.server.utils.UseCacheResource; +import org.caosdb.server.utils.UseCacheResourceDelegate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +/** + * The NoOpCache is used when the caching is diabled (globally or for the current transaction. + * + * <p>It results in much simpler code when the callers do not need to check whether the caching is + * active all the time. + * + * @author Timm Fitschen <t.fitschen@indiscale.com> + */ +@SuppressWarnings("rawtypes") +class NoOpCache implements Map { + + @Override + public int size() { + // TODO Auto-generated method stub + return 0; + } + + @Override + public boolean isEmpty() { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean containsKey(Object key) { + return false; + } + + @Override + public boolean containsValue(Object value) { + return false; + } + + @Override + public Object get(Object key) { + return null; + } + + @Override + public Object put(Object key, Object value) { + return null; + } + + @Override + public Object remove(Object key) { + return null; + } + + @Override + public void putAll(Map m) {} + + @Override + public void clear() {} + + @Override + public Set keySet() { + return Collections.EMPTY_SET; + } + + @Override + public Collection values() { + return Collections.EMPTY_LIST; + } + + @Override + public Set entrySet() { + return Collections.EMPTY_SET; + } +} + +/** + * Abstract base implementation for all EntityTransactions (Transaction involving entities). + * + * <p>Handles caching, benchmark timing, the access to the back end, generates and checks entity + * ids, holds the state of the transaction and triggers the scheduler to run the jobs. + * + * @author Timm Fitschen <t.fitschen@indiscale.com> + */ public abstract class Transaction<C extends TransactionContainer> extends AbstractObservable - implements TransactionInterface { + implements EntityTransactionInterface, UseCacheResource { @Override public TransactionBenchmark getTransactionBenchmark() { @@ -62,6 +147,8 @@ public abstract class Transaction<C extends TransactionContainer> extends Abstra private static final DatabaseAccessManager accessManager = DatabaseAccessManager.getInstance(); public static final String CLEAN_UP = "TransactionCleanUp"; + + protected EntityIdRegistry idRegistry = null; private final C container; private Access access = null; private final Schedule schedule = new Schedule(); @@ -108,11 +195,12 @@ public abstract class Transaction<C extends TransactionContainer> extends Abstra this.schedule.addAll(Job.loadPermanentContainerJobs(this)); for (final EntityInterface e : getContainer()) { + if (e.skipJob()) continue; final List<Job> loadJobs = loadContainerFlags.loadJobs(e, this); this.schedule.addAll(loadJobs); // additionally load datatype job - if (e.hasValue()) { + if (e.hasValue() || e.hasDatatype()) { this.schedule.add(new CheckDatatypePresent().init(JobFailureSeverity.ERROR, e, this)); } } @@ -267,61 +355,104 @@ public abstract class Transaction<C extends TransactionContainer> extends Abstra } } - /** @see {@link #execute()} */ + /** + * @see {@link #execute()} + */ protected void rollBack() { this.schedule.runJobs(TransactionStage.ROLL_BACK); } - /** @see {@link #execute()} */ + /** + * @see {@link #execute()} + */ protected abstract void init() throws Exception; - /** @see {@link #execute()} */ + /** + * @see {@link #execute()} + */ protected abstract void preCheck() throws InterruptedException, Exception; - /** @see {@link #execute()} */ + /** + * @see {@link #execute()} + */ protected final void check() { this.schedule.runJobs(TransactionStage.CHECK); } - /** @see {@link #execute()} */ + /** + * @see {@link #execute()} + */ protected abstract void postCheck(); - /** @see {@link #execute()} */ + /** + * @see {@link #execute()} + */ protected abstract void preTransaction() throws InterruptedException; - /** @see {@link #execute()} */ + /** + * @see {@link #execute()} + */ protected abstract void transaction() throws Exception; - /** @see {@link #execute()} */ + /** + * @see {@link #execute()} + */ protected abstract void postTransaction() throws Exception; - /** @see {@link #execute()} */ + /** + * @see {@link #execute()} + */ protected abstract void cleanUp(); - /** @see {@link #execute()} */ + /** + * @see {@link #execute()} + */ protected void commit() throws Exception {} - public boolean useCache() { - return getContainer().getFlags() != null - && !getContainer().getFlags().containsKey("disableCache"); - } - public final Access getAccess() { return this.access; } protected void setAccess(final Access a) { this.access = a; + if (access != null) { + this.access.setUseCacheResourceDelegate(useCacheResourceDelegate); + } } @SuppressWarnings("unchecked") - public <S, T> HashMap<S, T> getCache(final String name) { + public <S, T> Map<S, T> getCache(final String name) { + if (!useCache()) { + return NO_OP_CACHE; + } if (!this.caches.containsKey(name)) { this.caches.put(name, new HashMap<S, T>()); } return this.caches.get(name); } + private static final NoOpCache NO_OP_CACHE = new NoOpCache(); + @SuppressWarnings("rawtypes") - private final HashMap<String, HashMap> caches = new HashMap<>(); + private final Map<String, Map> caches = new HashMap<>(); + + private final UseCacheResource useCacheResourceDelegate = new UseCacheResourceDelegate(); + + @Override + public UseCacheResource getUseCacheResourceDelegate() { + return this.useCacheResourceDelegate.getUseCacheResourceDelegate(); + } + + @Override + public void setUseCacheResourceDelegate(UseCacheResource delegate) { + this.useCacheResourceDelegate.setUseCacheResourceDelegate(delegate); + } + + @Override + public boolean matchIdPattern(String id) { + if (this.idRegistry == null) { + this.idRegistry = new EntityIdRegistry(this); + } + return idRegistry.matchIdPattern(id); + } } diff --git a/src/main/java/org/caosdb/server/transaction/UpdateACL.java b/src/main/java/org/caosdb/server/transaction/UpdateACL.java index 84c73080550d8899f4f1a5156bebb2a44c39df6a..4bb27c2399d97bca0035ea27b5f7e4797428b18e 100644 --- a/src/main/java/org/caosdb/server/transaction/UpdateACL.java +++ b/src/main/java/org/caosdb/server/transaction/UpdateACL.java @@ -67,7 +67,8 @@ public class UpdateACL extends Transaction<TransactionContainer> oldContainer.add(new UpdateEntity(e.getId(), null)); } - RetrieveFullEntityTransaction t = new RetrieveFullEntityTransaction(oldContainer); + RetrieveFullEntityTransaction t = + new RetrieveFullEntityTransaction(oldContainer, getTransactor()); execute(t, getAccess()); // the entities in this container only have an id and an ACL. -> Replace @@ -143,4 +144,10 @@ public class UpdateACL extends Transaction<TransactionContainer> public String getSRID() { return getContainer().getRequestId(); } + + @Override + public String generateId() { + throw new UnsupportedOperationException( + "This is not implemented on purpose. This exception indicates a usage error of this UpdateACL class."); + } } diff --git a/src/main/java/org/caosdb/server/transaction/UpdateUserTransaction.java b/src/main/java/org/caosdb/server/transaction/UpdateUserTransaction.java index 6808968f20574242cfdaa9b87793d7a4ad4b6914..7730d82b2fc2fb7ff3d34430ea3d1b60ffb9c16e 100644 --- a/src/main/java/org/caosdb/server/transaction/UpdateUserTransaction.java +++ b/src/main/java/org/caosdb/server/transaction/UpdateUserTransaction.java @@ -63,7 +63,7 @@ public class UpdateUserTransaction extends AccessControlTransaction { final String username, final UserStatus status, final String email, - final Integer entity, + final String entity, final String password) { this.user = new ProtoUser(); this.user.realm = @@ -177,7 +177,7 @@ public class UpdateUserTransaction extends AccessControlTransaction { ACMPermissions.PERMISSION_UPDATE_USER_ENTITY(this.user.realm, this.user.name)); isToBeUpdated = true; - if (this.user.entity.equals(0)) { + if (this.user.entity.isEmpty()) { // this means that the entity is to be reset. this.user.entity = null; } else { diff --git a/src/main/java/org/caosdb/server/transaction/WriteTransaction.java b/src/main/java/org/caosdb/server/transaction/WriteTransaction.java index 0841a4956d71d6b22c4dcd46bac0e8befc5dc4b3..a35b7a595a15f2b9ace5d902c7492cc0fb182eb4 100644 --- a/src/main/java/org/caosdb/server/transaction/WriteTransaction.java +++ b/src/main/java/org/caosdb/server/transaction/WriteTransaction.java @@ -3,8 +3,8 @@ * * Copyright (C) 2018 Research Group Biomedical Physics, * Max-Planck-Institute for Dynamics and Self-Organization Göttingen - * Copyright (C) 2019-2022 IndiScale GmbH <info@indiscale.com> - * Copyright (C) 2019-2022 Timm Fitschen <t.fitschen@indiscale.com> + * Copyright (C) 2019-2023 IndiScale GmbH <info@indiscale.com> + * Copyright (C) 2019-2023 Timm Fitschen <t.fitschen@indiscale.com> * Copyright (C) 2022 Daniel Hornung <d.hornung@indiscale.com> * * This program is free software: you can redistribute it and/or modify @@ -40,6 +40,7 @@ import org.caosdb.server.database.backend.transaction.UpdateEntityTransaction; import org.caosdb.server.database.misc.RollBackHandler; import org.caosdb.server.entity.DeleteEntity; import org.caosdb.server.entity.EntityID; +import org.caosdb.server.entity.EntityIdRegistry; import org.caosdb.server.entity.EntityInterface; import org.caosdb.server.entity.InsertEntity; import org.caosdb.server.entity.Message; @@ -168,10 +169,10 @@ public class WriteTransaction extends Transaction<WritableContainer> // Retrieve a container which contains all IDs of those entities // which are to be updated. - execute(new RetrieveFullEntityTransaction(oldContainer), getAccess()); + execute(new RetrieveFullEntityTransaction(oldContainer, getTransactor()), getAccess()); // Retrieve all entities which are to be deleted. - execute(new RetrieveFullEntityTransaction(deleteContainer), getAccess()); + execute(new RetrieveFullEntityTransaction(deleteContainer, getTransactor()), getAccess()); // Check if any updates are to be processed. for (final EntityInterface entity : getContainer()) { @@ -378,14 +379,17 @@ public class WriteTransaction extends Transaction<WritableContainer> } // new datatype? - if (newEntity.hasDatatype() + if ((newEntity.hasDatatype() && oldEntity.hasDatatype() - && !newEntity.getDatatype().equals(oldEntity.getDatatype()) + && !newEntity.getDatatype().equals(oldEntity.getDatatype())) || newEntity.hasDatatype() ^ oldEntity.hasDatatype()) { needPermissions.add(EntityPermission.UPDATE_DATA_TYPE); updatetable = true; } else { - newEntity.setDatatypeOverride(oldEntity.isDatatypeOverride()); + if (oldEntity.hasDatatype()) { + newEntity.setDatatypeOverride(oldEntity.isDatatypeOverride()); + newEntity.setDatatype(oldEntity.getDatatype()); + } } // entity role @@ -584,4 +588,13 @@ public class WriteTransaction extends Transaction<WritableContainer> public String getSRID() { return getContainer().getRequestId(); } + + @Override + public String generateId() { + if (this.idRegistry == null) { + this.idRegistry = new EntityIdRegistry(this); + } + + return this.idRegistry.generate(); + } } diff --git a/src/main/java/org/caosdb/server/transaction/WriteTransactionInterface.java b/src/main/java/org/caosdb/server/transaction/WriteTransactionInterface.java index 4e2938e18d061cbd1a7575baa59322356731859a..c39d2c14a7dc625b1aa8bf0529feaeec2c4ca8b3 100644 --- a/src/main/java/org/caosdb/server/transaction/WriteTransactionInterface.java +++ b/src/main/java/org/caosdb/server/transaction/WriteTransactionInterface.java @@ -10,4 +10,6 @@ public interface WriteTransactionInterface extends TransactionInterface { public Subject getTransactor(); public String getSRID(); + + public String generateId(); } diff --git a/src/main/java/org/caosdb/server/utils/AbstractObservable.java b/src/main/java/org/caosdb/server/utils/AbstractObservable.java index abc7f0a0ae1cdcbeff613c2778637de97a507c57..1bb4fc472a90c05d656f0d4250299801b73f07a2 100644 --- a/src/main/java/org/caosdb/server/utils/AbstractObservable.java +++ b/src/main/java/org/caosdb/server/utils/AbstractObservable.java @@ -37,7 +37,9 @@ public abstract class AbstractObservable implements Observable { return this.observers.add(o); } - /** @param e A String denoting the notification event. */ + /** + * @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/ServerMessages.java b/src/main/java/org/caosdb/server/utils/ServerMessages.java index 25d52bc01a9c21c1a1c3fc799222052e1863ec2d..cc963ccf759ca73cc4f54160ae40db1d83c87546 100644 --- a/src/main/java/org/caosdb/server/utils/ServerMessages.java +++ b/src/main/java/org/caosdb/server/utils/ServerMessages.java @@ -618,4 +618,8 @@ public class ServerMessages { return new Message( MessageType.Error, MessageCode.MESSAGE_CODE_UNKNOWN, "You cannot delete yourself."); } + + public static Message QUERY_PARSING_ERROR(String message) { + return new Message(MessageType.Error, MessageCode.MESSAGE_CODE_QUERY_PARSING_ERROR, message); + } } diff --git a/src/main/java/org/caosdb/server/utils/UseCacheResource.java b/src/main/java/org/caosdb/server/utils/UseCacheResource.java new file mode 100644 index 0000000000000000000000000000000000000000..89057f51c59f0f99a3eed3346ec18ab29ada74c0 --- /dev/null +++ b/src/main/java/org/caosdb/server/utils/UseCacheResource.java @@ -0,0 +1,41 @@ +/* + * This file is a part of the CaosDB Project. + * + * Copyright (C) 2023 IndiScale GmbH <info@indiscale.com> + * Copyright (C) 2023 Timm Fitschen <t.fitschen@indiscale.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ +package org.caosdb.server.utils; + +/** + * Interface which indicates that a class can turn on and off the caching of the current transaction + * and inform the caller about the state of the transactions's caching. + * + * @author Timm Fitschen <t.fitschen@indiscale.com> + */ +public interface UseCacheResource { + + public default boolean useCache() { + return getUseCacheResourceDelegate().useCache(); + } + + public default void setUseCache(boolean useCache) { + getUseCacheResourceDelegate().setUseCache(useCache); + } + + public void setUseCacheResourceDelegate(UseCacheResource delegate); + + public UseCacheResource getUseCacheResourceDelegate(); +} diff --git a/src/main/java/org/caosdb/server/utils/UseCacheResourceDelegate.java b/src/main/java/org/caosdb/server/utils/UseCacheResourceDelegate.java new file mode 100644 index 0000000000000000000000000000000000000000..c537f0a813fe149b7338909f0dc95e8f3728dec1 --- /dev/null +++ b/src/main/java/org/caosdb/server/utils/UseCacheResourceDelegate.java @@ -0,0 +1,70 @@ +/* + * This file is a part of the CaosDB Project. + * + * Copyright (C) 2023 IndiScale GmbH <info@indiscale.com> + * Copyright (C) 2023 Timm Fitschen <t.fitschen@indiscale.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ +package org.caosdb.server.utils; + +import org.caosdb.server.CaosDBServer; + +/** + * Basic implementation of the {@link UseCacheResource} interface. + * + * <p>This implementation uses a Delegator Pattern to link different UseCacheResources together. + * This is uses to delegate the responsibility to the transaction as the final Object of the + * delegation. + * + * @author Timm Fitschen <t.fitschen@indiscale.com> + */ +public class UseCacheResourceDelegate implements UseCacheResource { + private boolean useCache = CaosDBServer.useCache(); + private UseCacheResource delegate = null; + + @Override + public void setUseCache(boolean useCache) { + if (this.delegate != null) { + this.delegate.setUseCache(useCache); + } else { + this.useCache = useCache; + } + } + + @Override + public boolean useCache() { + if (delegate != null) { + return delegate.useCache(); + } + return CaosDBServer.useCache() && useCache; + } + + @Override + public void setUseCacheResourceDelegate(UseCacheResource delegate) { + if (delegate == this) { + this.delegate = null; + } else { + this.delegate = delegate; + } + } + + @Override + public UseCacheResource getUseCacheResourceDelegate() { + if (this.delegate != null) { + return this.delegate; + } + return this; + } +} diff --git a/src/main/java/org/caosdb/server/utils/WebinterfaceUtils.java b/src/main/java/org/caosdb/server/utils/WebinterfaceUtils.java index 375d6b5e7bbced84cec5bdcdee26a9e436f0215a..6c583fccce77471ae4a4272f35d94ec2c886afd3 100644 --- a/src/main/java/org/caosdb/server/utils/WebinterfaceUtils.java +++ b/src/main/java/org/caosdb/server/utils/WebinterfaceUtils.java @@ -61,6 +61,23 @@ public class WebinterfaceUtils { private long buildNumberDate; private static final Map<String, WebinterfaceUtils> instances = new HashMap<>(); + public static String getForwardedProto(Request request) { + String scheme = null; + String forwarded = request.getHeaders().getFirstValue("Forwarded", true); + if (forwarded != null) { + for (String directive : forwarded.split(";")) { + String[] s = directive.split("=", 2); + if (s.length == 2 && "proto".equalsIgnoreCase(s[0])) { + scheme = s[1]; + } + } + } + if (scheme == null) { + scheme = request.getHeaders().getFirstValue("X-Forwarded-Proto", true); + } + return scheme; + } + /** * Retrieve an instance of {@link WebinterfaceUtils} for the request. The instance can be shared * with other callers. @@ -70,7 +87,7 @@ public class WebinterfaceUtils { */ public static WebinterfaceUtils getInstance(Request request) { String hostStr = request.getHostRef().getHostIdentifier(); - String scheme = request.getHeaders().getFirstValue("X-Forwarded-Proto", true); + String scheme = getForwardedProto(request); if (scheme != null) { hostStr = hostStr.replaceFirst("^" + request.getHostRef().getScheme(), scheme); } diff --git a/src/test/docker/Dockerfile b/src/test/docker/Dockerfile index 19cf9dcd5cae9431ae0dc265870acbdfb1b3e650..f0ae05e65389f4b89cb0cb1373a7475b054490cd 100644 --- a/src/test/docker/Dockerfile +++ b/src/test/docker/Dockerfile @@ -6,15 +6,14 @@ RUN apt-get update && \ libtiff5-dev libjpeg-dev libopenjp2-7-dev zlib1g-dev \ libfreetype6-dev liblcms2-dev libwebp-dev tcl8.6-dev tk8.6-dev python3-tk \ libharfbuzz-dev libfribidi-dev libxcb1-dev \ - python3-pip screen libpam0g-dev unzip curl shunit2 \ - python3-sphinx + python3-pip screen libpam0g-dev unzip curl shunit2 -RUN pip3 install javasphinx recommonmark sphinx-rtd-theme sphinxcontrib-plantuml sphinx-a4doc +RUN apt-get install -y \ + libcairo2-dev + +RUN pip3 install sphinx javasphinx recommonmark sphinx-rtd-theme sphinxcontrib-plantuml sphinx-a4doc # Alternative, if javasphinx fails because python3-sphinx is too recent: # (`_l` not found): # -# git clone git@github.com:simgrid/javasphinx.git -# cd javasphinx -# git checkout 659209069603a -# pip3 install . +RUN git clone https://github.com/simgrid/javasphinx.git && cd javasphinx && git checkout 659209069603a && pip3 install . diff --git a/src/test/java/org/caosdb/server/caching/TestCaching.java b/src/test/java/org/caosdb/server/caching/TestCaching.java index 1aba2ff6f16cea7a1f62e84e50e2cc9797114e27..90b642f17070e2f67312d533331ee7929117b447 100644 --- a/src/test/java/org/caosdb/server/caching/TestCaching.java +++ b/src/test/java/org/caosdb/server/caching/TestCaching.java @@ -1,3 +1,22 @@ +/* + * This file is a part of the CaosDB Project. + * + * Copyright (C) 2023 IndiScale GmbH <info@indiscale.com> + * Copyright (C) 2023 Timm Fitschen <t.fitschen@indiscale.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ package org.caosdb.server.caching; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -7,6 +26,7 @@ import java.io.IOException; import org.apache.commons.jcs.JCS; import org.apache.commons.jcs.access.CacheAccess; import org.caosdb.server.CaosDBServer; +import org.caosdb.server.ServerProperties; import org.caosdb.server.accessControl.Pam; import org.caosdb.server.database.backend.transaction.RetrieveProperties; import org.junit.jupiter.api.BeforeAll; @@ -17,6 +37,8 @@ public class TestCaching { @BeforeAll public static void init() throws IOException { CaosDBServer.initServerProperties(); + CaosDBServer.getServerProperties().setProperty(ServerProperties.KEY_CACHE_DISABLE, "FALSE"); + CaosDBServer.initCaching(); JCSCacheHelper.init(); } diff --git a/src/test/java/org/caosdb/server/caching/TestNoCaching.java b/src/test/java/org/caosdb/server/caching/TestNoCaching.java index 4fc73adf89f9355d1ff6addcd0f71fd9eda4e653..7eeeee71a909a106ad93b8c440ad895e366cca5f 100644 --- a/src/test/java/org/caosdb/server/caching/TestNoCaching.java +++ b/src/test/java/org/caosdb/server/caching/TestNoCaching.java @@ -1,3 +1,22 @@ +/* + * This file is a part of the CaosDB Project. + * + * Copyright (C) 2023 IndiScale GmbH <info@indiscale.com> + * Copyright (C) 2023 Timm Fitschen <t.fitschen@indiscale.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ package org.caosdb.server.caching; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -17,6 +36,7 @@ public class TestNoCaching { public static void init() throws IOException { CaosDBServer.initServerProperties(); CaosDBServer.setProperty(ServerProperties.KEY_CACHE_DISABLE, "TRUE"); + CaosDBServer.initCaching(); JCSCacheHelper.init(); } diff --git a/src/test/java/org/caosdb/server/database/InsertTest.java b/src/test/java/org/caosdb/server/database/InsertTest.java deleted file mode 100644 index 524e7addd20581db349082f91206605a97bdee8e..0000000000000000000000000000000000000000 --- a/src/test/java/org/caosdb/server/database/InsertTest.java +++ /dev/null @@ -1,391 +0,0 @@ -/* - * ** header v3.0 - * This file is a part of the CaosDB Project. - * - * Copyright (C) 2018 Research Group Biomedical Physics, - * Max-Planck-Institute for Dynamics and Self-Organization Göttingen - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <https://www.gnu.org/licenses/>. - * - * ** end header - */ -package org.caosdb.server.database; - -import static org.caosdb.server.database.DatabaseUtils.deriveStage1Inserts; -import static org.caosdb.server.database.DatabaseUtils.deriveStage2Inserts; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.LinkedList; -import org.caosdb.server.datatype.CollectionValue; -import org.caosdb.server.datatype.GenericValue; -import org.caosdb.server.datatype.SingleValue; -import org.caosdb.server.entity.Entity; -import org.caosdb.server.entity.EntityID; -import org.caosdb.server.entity.EntityInterface; -import org.caosdb.server.entity.InsertEntity; -import org.caosdb.server.entity.RetrieveEntity; -import org.caosdb.server.entity.Role; -import org.caosdb.server.entity.StatementStatus; -import org.caosdb.server.entity.wrapper.Property; -import org.junit.jupiter.api.Test; - -public class InsertTest { - - /** - * Record with very deep property tree. - * - * @throws Exception - */ - @Test - public void testTransformation1() throws Exception { - final Entity r = new InsertEntity("Test", Role.Record); - final Property p1 = new Property(new RetrieveEntity(new EntityID(1))); - p1.setRole("Property"); - p1.setValue(new GenericValue("V1")); - p1.setDatatype("TEXT"); - p1.setStatementStatus(StatementStatus.FIX); - r.addProperty(p1); - - final Property p2 = new Property(new RetrieveEntity(new EntityID(2))); - p2.setRole("Property"); - p2.setValue(new GenericValue("V2")); - p2.setDatatype("TEXT"); - p2.setStatementStatus(StatementStatus.RECOMMENDED); - r.addProperty(p2); - - final Property p3 = new Property(new RetrieveEntity(new EntityID(3))); - p3.setRole("Property"); - p3.setValue(new GenericValue("V3")); - p3.setDatatype("TEXT"); - p3.setStatementStatus(StatementStatus.OBLIGATORY); - p2.addProperty(p3); - - final Property p4 = new Property(new RetrieveEntity(new EntityID(4))); - p4.setRole("Property"); - p4.setValue(new GenericValue("V4")); - p4.setDatatype("TEXT"); - p4.setStatementStatus(StatementStatus.OBLIGATORY); - r.addProperty(p4); - - final Property p5 = new Property(new RetrieveEntity(new EntityID(5))); - p5.setRole("Property"); - p5.setValue(new GenericValue("V5")); - p5.setDatatype("TEXT"); - p5.setStatementStatus(StatementStatus.OBLIGATORY); - p4.addProperty(p5); - - final Property p6 = new Property(new RetrieveEntity(new EntityID(6))); - p6.setRole("Property"); - p6.setValue(new GenericValue("V6")); - p6.setDatatype("TEXT"); - p6.setStatementStatus(StatementStatus.OBLIGATORY); - p5.addProperty(p6); - - final Property p7 = new Property(new RetrieveEntity(new EntityID(7))); - p7.setRole("Property"); - p7.setValue(new GenericValue("V7")); - p7.setDatatype("TEXT"); - p7.setStatementStatus(StatementStatus.OBLIGATORY); - r.addProperty(p7); - - final Property p8 = new Property(new RetrieveEntity(new EntityID(8))); - p8.setRole("Property"); - p8.setValue(new GenericValue("V8")); - p8.setDatatype("TEXT"); - p8.setStatementStatus(StatementStatus.OBLIGATORY); - p7.addProperty(p8); - - final Property p9 = new Property(new RetrieveEntity(new EntityID(9))); - p9.setRole("Property"); - p9.setValue(new GenericValue("V9")); - p9.setDatatype("TEXT"); - p9.setStatementStatus(StatementStatus.OBLIGATORY); - p8.addProperty(p9); - - final Property p10 = new Property(new RetrieveEntity(new EntityID(10))); - p10.setRole("Property"); - p10.setValue(new GenericValue("V10")); - p10.setDatatype("TEXT"); - p10.setStatementStatus(StatementStatus.OBLIGATORY); - p9.addProperty(p10); - - final LinkedList<EntityInterface> stage1Inserts = new LinkedList<EntityInterface>(); - final LinkedList<EntityInterface> stage2Inserts = new LinkedList<EntityInterface>(); - new LinkedList<EntityInterface>(); - deriveStage1Inserts(stage1Inserts, r); - - assertEquals(7, stage1Inserts.size()); - assertEquals(Role.Property, stage1Inserts.get(0).getRole()); - assertEquals(new EntityID(1), stage1Inserts.get(0).getId()); - assertEquals("V1", ((SingleValue) stage1Inserts.get(0).getValue()).toDatabaseString()); - assertFalse(stage1Inserts.get(0).hasReplacement()); - - assertEquals(Role.Property, stage1Inserts.get(1).getRole()); - assertEquals(new EntityID(2), stage1Inserts.get(1).getId()); - assertEquals("V2", ((SingleValue) stage1Inserts.get(1).getValue()).toDatabaseString()); - assertFalse(stage1Inserts.get(1).hasReplacement()); - - assertEquals(Role.Property, stage1Inserts.get(2).getRole()); - assertEquals(new EntityID(4), stage1Inserts.get(2).getId()); - assertEquals("V4", ((SingleValue) stage1Inserts.get(2).getValue()).toDatabaseString()); - assertFalse(stage1Inserts.get(2).hasReplacement()); - - assertEquals(Role.Domain, stage1Inserts.get(3).getRole()); - assertEquals("V5", ((SingleValue) stage1Inserts.get(3).getValue()).toDatabaseString()); - assertFalse(stage1Inserts.get(3).hasReplacement()); - - assertEquals(Role.Property, stage1Inserts.get(4).getRole()); - assertEquals(new EntityID(7), stage1Inserts.get(4).getId()); - assertEquals("V7", ((SingleValue) stage1Inserts.get(4).getValue()).toDatabaseString()); - assertFalse(stage1Inserts.get(4).hasReplacement()); - - assertEquals(Role.Domain, stage1Inserts.get(5).getRole()); - assertEquals("V8", ((SingleValue) stage1Inserts.get(5).getValue()).toDatabaseString()); - assertFalse(stage1Inserts.get(5).hasReplacement()); - - assertEquals(Role.Domain, stage1Inserts.get(6).getRole()); - assertEquals("V9", ((SingleValue) stage1Inserts.get(6).getValue()).toDatabaseString()); - assertFalse(stage1Inserts.get(6).hasReplacement()); - - deriveStage2Inserts(stage2Inserts, stage1Inserts); - - assertEquals(6, stage2Inserts.size()); - assertEquals(Role.Property, stage2Inserts.get(0).getRole()); - assertEquals(new EntityID(3), stage2Inserts.get(0).getId()); - assertEquals("V3", ((SingleValue) stage2Inserts.get(0).getValue()).toDatabaseString()); - assertFalse(stage2Inserts.get(0).hasReplacement()); - - assertEquals(Role.Property, stage2Inserts.get(1).getRole()); - assertEquals(new EntityID(5), stage2Inserts.get(1).getId()); - assertEquals("V5", ((SingleValue) stage2Inserts.get(1).getValue()).toDatabaseString()); - assertTrue(stage2Inserts.get(1).hasReplacement()); - assertEquals(stage1Inserts.get(3), stage2Inserts.get(1).getReplacement()); - - assertEquals(Role.Property, stage2Inserts.get(2).getRole()); - assertEquals(new EntityID(6), stage2Inserts.get(2).getId()); - assertEquals("V6", ((SingleValue) stage2Inserts.get(2).getValue()).toDatabaseString()); - assertFalse(stage2Inserts.get(2).hasReplacement()); - - assertEquals(Role.Property, stage2Inserts.get(3).getRole()); - assertEquals(new EntityID(8), stage2Inserts.get(3).getId()); - assertEquals("V8", ((SingleValue) stage2Inserts.get(3).getValue()).toDatabaseString()); - assertTrue(stage2Inserts.get(3).hasReplacement()); - assertEquals(stage1Inserts.get(5), stage2Inserts.get(3).getReplacement()); - - assertEquals(Role.Property, stage2Inserts.get(4).getRole()); - assertEquals(new EntityID(9), stage2Inserts.get(4).getId()); - assertEquals("V9", ((SingleValue) stage2Inserts.get(4).getValue()).toDatabaseString()); - assertTrue(stage2Inserts.get(4).hasReplacement()); - assertEquals(stage1Inserts.get(6), stage2Inserts.get(4).getReplacement()); - - assertEquals(Role.Property, stage2Inserts.get(5).getRole()); - assertEquals(new EntityID(10), stage2Inserts.get(5).getId()); - assertEquals("V10", ((SingleValue) stage2Inserts.get(5).getValue()).toDatabaseString()); - assertFalse(stage2Inserts.get(5).hasReplacement()); - } - - /** - * Record with two properties of the same id and different values. One has a sub property, the - * other does not. - * - * @throws Exception - */ - @Test - public void testTransformation2() throws Exception { - final Entity r = new InsertEntity("Test", Role.Record); - final Property p1 = new Property(new RetrieveEntity(new EntityID(1))); - p1.setRole("Property"); - p1.setValue(new GenericValue("V1-1")); - p1.setDatatype("TEXT"); - p1.setStatementStatus(StatementStatus.FIX); - r.addProperty(p1); - - final Property p2 = new Property(new RetrieveEntity(new EntityID(1))); - p2.setRole("Property"); - p2.setValue(new GenericValue("V1-2")); - p2.setDatatype("TEXT"); - p2.setStatementStatus(StatementStatus.FIX); - r.addProperty(p2); - - final Property subp = new Property(new RetrieveEntity(new EntityID(2))); - subp.setRole("Property"); - subp.setValue(new GenericValue("V2")); - subp.setDatatype("TEXT"); - subp.setStatementStatus(StatementStatus.FIX); - p2.addProperty(subp); - - final LinkedList<EntityInterface> stage1Inserts = new LinkedList<EntityInterface>(); - final LinkedList<EntityInterface> stage2Inserts = new LinkedList<EntityInterface>(); - - deriveStage1Inserts(stage1Inserts, r); - - assertEquals(3, stage1Inserts.size()); - assertEquals(Role.Property, stage1Inserts.get(0).getRole()); - assertEquals(new EntityID(1), stage1Inserts.get(0).getId()); - assertEquals("V1-1", ((SingleValue) stage1Inserts.get(0).getValue()).toDatabaseString()); - assertFalse(stage1Inserts.get(0).hasReplacement()); - - assertEquals(Role.Property, stage1Inserts.get(1).getRole()); - assertEquals("V1-2", ((SingleValue) stage1Inserts.get(1).getValue()).toDatabaseString()); - assertEquals(new EntityID(1), stage1Inserts.get(1).getId()); - assertTrue(stage1Inserts.get(1).hasReplacement()); - assertEquals(stage1Inserts.get(2), stage1Inserts.get(1).getReplacement()); - - assertEquals(Role.Domain, stage1Inserts.get(2).getRole()); - assertEquals("V1-2", ((SingleValue) stage1Inserts.get(2).getValue()).toDatabaseString()); - assertFalse(stage1Inserts.get(2).hasReplacement()); - - deriveStage2Inserts(stage2Inserts, stage1Inserts); - - assertEquals(1, stage2Inserts.size()); - assertEquals(Role.Property, stage2Inserts.get(0).getRole()); - assertEquals(new EntityID(2), stage2Inserts.get(0).getId()); - assertEquals("V2", ((SingleValue) stage2Inserts.get(0).getValue()).toDatabaseString()); - assertFalse(stage2Inserts.get(0).hasReplacement()); - } - - /** - * Record with three properties of the same id without any value. The first has a sub property, - * the others do not. - * - * @throws Exception - */ - @Test - public void testTransformation3() throws Exception { - final Entity r = new InsertEntity("Test", Role.Record); - final Property p1 = new Property(new RetrieveEntity(new EntityID(1))); - p1.setRole("Property"); - p1.setDatatype("TEXT"); - p1.setStatementStatus(StatementStatus.FIX); - r.addProperty(p1); - - final Property p2 = new Property(new RetrieveEntity(new EntityID(1))); - p2.setRole("Property"); - p2.setDatatype("TEXT"); - p2.setStatementStatus(StatementStatus.FIX); - r.addProperty(p2); - - final Property p3 = new Property(new RetrieveEntity(new EntityID(1))); - p3.setRole("Property"); - p3.setDatatype("TEXT"); - p3.setStatementStatus(StatementStatus.FIX); - r.addProperty(p3); - - final Property sub1 = new Property(new RetrieveEntity(new EntityID(2))); - sub1.setRole("Property"); - sub1.setDatatype("TEXT"); - sub1.setValue(new GenericValue("V1")); - sub1.setStatementStatus(StatementStatus.FIX); - p1.addProperty(sub1); - - final LinkedList<EntityInterface> stage1Inserts = new LinkedList<EntityInterface>(); - final LinkedList<EntityInterface> stage2Inserts = new LinkedList<EntityInterface>(); - - deriveStage1Inserts(stage1Inserts, r); - - assertEquals(4, stage1Inserts.size()); - assertEquals(Role.Property, stage1Inserts.get(0).getRole()); - assertEquals(new EntityID(1), stage1Inserts.get(0).getId()); - assertTrue(stage1Inserts.get(0).hasReplacement()); - assertEquals(stage1Inserts.get(1), stage1Inserts.get(0).getReplacement()); - assertEquals(null, stage1Inserts.get(0).getValue()); - - assertEquals(Role.Domain, stage1Inserts.get(1).getRole()); - assertFalse(stage1Inserts.get(1).hasReplacement()); - assertEquals(null, stage1Inserts.get(1).getValue()); - - assertEquals(Role.Property, stage1Inserts.get(2).getRole()); - assertEquals(new EntityID(1), stage1Inserts.get(2).getId()); - assertFalse(stage1Inserts.get(2).hasReplacement()); - assertEquals(null, stage1Inserts.get(2).getValue()); - - assertEquals(Role.Property, stage1Inserts.get(3).getRole()); - assertEquals(new EntityID(1), stage1Inserts.get(3).getId()); - assertFalse(stage1Inserts.get(3).hasReplacement()); - assertEquals(null, stage1Inserts.get(3).getValue()); - - deriveStage2Inserts(stage2Inserts, stage1Inserts); - - assertEquals(1, stage2Inserts.size()); - assertEquals(Role.Property, stage2Inserts.get(0).getRole()); - assertEquals(new EntityID(2), stage2Inserts.get(0).getId()); - assertEquals("V1", ((SingleValue) stage2Inserts.get(0).getValue()).toDatabaseString()); - assertFalse(stage2Inserts.get(0).hasReplacement()); - } - - /** - * Record with a list property. - * - * @throws Exception - */ - @Test - public void testTransformation4() throws Exception { - - final Entity r = new InsertEntity("Test", Role.Record); - final Property p1 = new Property(new RetrieveEntity(new EntityID(1))); - p1.setRole("Property"); - p1.setDatatype("TEXT"); - p1.setValue(new GenericValue("V1")); - p1.setStatementStatus(StatementStatus.FIX); - r.addProperty(p1); - - final Property p2 = new Property(new RetrieveEntity(new EntityID(2))); - p2.setRole("Property"); - p2.setDatatype("LIST<TEXT>"); - p2.setStatementStatus(StatementStatus.FIX); - final CollectionValue vals = new CollectionValue(); - vals.add(new GenericValue("L1")); - vals.add(new GenericValue("L2")); - vals.add(new GenericValue("L3")); - p2.setValue(vals); - r.addProperty(p2); - - final Property p3 = new Property(new RetrieveEntity(new EntityID(3))); - p3.setRole("Property"); - p3.setDatatype("TEXT"); - p3.setValue(new GenericValue("V3")); - p3.setStatementStatus(StatementStatus.FIX); - r.addProperty(p3); - - final LinkedList<EntityInterface> stage1Inserts = new LinkedList<EntityInterface>(); - final LinkedList<EntityInterface> stage2Inserts = new LinkedList<EntityInterface>(); - - deriveStage1Inserts(stage1Inserts, r); - - assertEquals(4, stage1Inserts.size()); - assertEquals(Role.Property, stage1Inserts.get(0).getRole()); - assertEquals(new EntityID(1), stage1Inserts.get(0).getId()); - assertFalse(stage1Inserts.get(0).hasReplacement()); - assertEquals("V1", ((SingleValue) stage1Inserts.get(0).getValue()).toDatabaseString()); - - assertEquals(Role.Property, stage1Inserts.get(1).getRole()); - assertEquals(new EntityID(2), stage1Inserts.get(1).getId()); - assertTrue(stage1Inserts.get(1).hasReplacement()); - assertEquals(stage1Inserts.get(2), stage1Inserts.get(1).getReplacement()); - assertTrue(stage1Inserts.get(1).getValue() instanceof CollectionValue); - - assertEquals(Role.Domain, stage1Inserts.get(2).getRole()); - assertFalse(stage1Inserts.get(2).hasReplacement()); - assertTrue(stage1Inserts.get(2).getValue() instanceof CollectionValue); - - assertEquals(Role.Property, stage1Inserts.get(3).getRole()); - assertEquals(new EntityID(3), stage1Inserts.get(3).getId()); - assertFalse(stage1Inserts.get(3).hasReplacement()); - assertEquals("V3", ((SingleValue) stage1Inserts.get(3).getValue()).toDatabaseString()); - - deriveStage2Inserts(stage2Inserts, stage1Inserts); - } -} diff --git a/src/test/java/org/caosdb/server/database/backend/implementation/MySQL/InsertTest.java b/src/test/java/org/caosdb/server/database/backend/implementation/MySQL/InsertTest.java new file mode 100644 index 0000000000000000000000000000000000000000..e8749510b906a12c97daed5bf3971f826d26d477 --- /dev/null +++ b/src/test/java/org/caosdb/server/database/backend/implementation/MySQL/InsertTest.java @@ -0,0 +1,505 @@ +/* + * This file is a part of the CaosDB Project. + * + * Copyright (C) 2018 Research Group Biomedical Physics, + * Max-Planck-Institute for Dynamics and Self-Organization Göttingen + * Copyright (C) 2023 IndiScale GmbH <info@indiscale.com> + * Copyright (C) 2023 Timm Fitschen <t.fitschen@indiscale.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ +package org.caosdb.server.database.backend.implementation.MySQL; + +import static org.caosdb.server.database.backend.implementation.MySQL.DatabaseUtils.deriveStage1Inserts; +import static org.caosdb.server.database.backend.implementation.MySQL.DatabaseUtils.deriveStage2Inserts; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.LinkedList; +import java.util.List; +import org.caosdb.server.datatype.CollectionValue; +import org.caosdb.server.datatype.GenericValue; +import org.caosdb.server.datatype.ReferenceValue; +import org.caosdb.server.datatype.SingleValue; +import org.caosdb.server.entity.Entity; +import org.caosdb.server.entity.EntityID; +import org.caosdb.server.entity.InsertEntity; +import org.caosdb.server.entity.RetrieveEntity; +import org.caosdb.server.entity.Role; +import org.caosdb.server.entity.StatementStatus; +import org.caosdb.server.entity.wrapper.Property; +import org.junit.jupiter.api.Test; + +public class InsertTest { + + private Deque<EntityID> registerReplacementIds(int count) { + Deque<EntityID> replacementIds = new ArrayDeque<>(); + for (int i = 1; i < count + 1; i++) { + replacementIds.add(new EntityID(Integer.toString(-i))); + } + return replacementIds; + } + + /** + * Record with very deep property tree. + * + * @throws Exception + */ + @Test + public void testTransformation1() throws Exception { + final Entity r = new InsertEntity("Test", Role.Record); + final Property p1 = new Property(new RetrieveEntity(new EntityID("1"))); + p1.setRole("Property"); + p1.setValue(new GenericValue("V1")); + p1.setDatatype("TEXT"); + p1.setStatementStatus(StatementStatus.FIX); + r.addProperty(p1); + + final Property p2 = new Property(new RetrieveEntity(new EntityID("2"))); + p2.setRole("Property"); + p2.setValue(new GenericValue("V2")); + p2.setDatatype("TEXT"); + p2.setStatementStatus(StatementStatus.RECOMMENDED); + r.addProperty(p2); + + final Property p3 = new Property(new RetrieveEntity(new EntityID("3"))); + p3.setRole("Property"); + p3.setValue(new GenericValue("V3")); + p3.setDatatype("TEXT"); + p3.setStatementStatus(StatementStatus.OBLIGATORY); + p2.addProperty(p3); + + final Property p4 = new Property(new RetrieveEntity(new EntityID("4"))); + p4.setRole("Property"); + p4.setValue(new GenericValue("V4")); + p4.setDatatype("TEXT"); + p4.setStatementStatus(StatementStatus.OBLIGATORY); + r.addProperty(p4); + + final Property p5 = new Property(new RetrieveEntity(new EntityID("5"))); + p5.setRole("Property"); + p5.setValue(new GenericValue("V5")); + p5.setDatatype("TEXT"); + p5.setStatementStatus(StatementStatus.OBLIGATORY); + p4.addProperty(p5); + + final Property p6 = new Property(new RetrieveEntity(new EntityID("6"))); + p6.setRole("Property"); + p6.setValue(new GenericValue("V6")); + p6.setDatatype("TEXT"); + p6.setStatementStatus(StatementStatus.OBLIGATORY); + p5.addProperty(p6); + + final Property p7 = new Property(new RetrieveEntity(new EntityID("7"))); + p7.setRole("Property"); + p7.setValue(new GenericValue("V7")); + p7.setDatatype("TEXT"); + p7.setStatementStatus(StatementStatus.OBLIGATORY); + r.addProperty(p7); + + final Property p8 = new Property(new RetrieveEntity(new EntityID("8"))); + p8.setRole("Property"); + p8.setValue(new GenericValue("V8")); + p8.setDatatype("TEXT"); + p8.setStatementStatus(StatementStatus.OBLIGATORY); + p7.addProperty(p8); + + final Property p9 = new Property(new RetrieveEntity(new EntityID("9"))); + p9.setRole("Property"); + p9.setValue(new GenericValue("V9")); + p9.setDatatype("TEXT"); + p9.setStatementStatus(StatementStatus.OBLIGATORY); + p8.addProperty(p9); + + final Property p10 = new Property(new RetrieveEntity(new EntityID("10"))); + p10.setRole("Property"); + p10.setValue(new GenericValue("V10")); + p10.setDatatype("TEXT"); + p10.setStatementStatus(StatementStatus.OBLIGATORY); + p9.addProperty(p10); + + final LinkedList<Property> stage1Inserts = new LinkedList<>(); + final LinkedList<Property> stage2Inserts = new LinkedList<>(); + + int replacementCount = deriveStage1Inserts(stage1Inserts, r); + assertEquals(3, replacementCount); + + deriveStage2Inserts(stage2Inserts, stage1Inserts, registerReplacementIds(replacementCount), r); + + assertEquals(7, stage1Inserts.size()); + assertFalse(stage1Inserts.get(0) instanceof Replacement); + assertEquals("1", stage1Inserts.get(0).getId().toString()); + assertEquals("V1", ((SingleValue) stage1Inserts.get(0).getValue()).toDatabaseString()); + + assertFalse(stage1Inserts.get(1) instanceof Replacement); + assertEquals("2", stage1Inserts.get(1).getId().toString()); + assertEquals("V2", ((SingleValue) stage1Inserts.get(1).getValue()).toDatabaseString()); + + assertFalse(stage1Inserts.get(2) instanceof Replacement); + assertEquals("4", stage1Inserts.get(2).getId().toString()); + assertEquals("V4", ((SingleValue) stage1Inserts.get(2).getValue()).toDatabaseString()); + + assertTrue(stage1Inserts.get(3) instanceof Replacement); + assertEquals("-1", stage1Inserts.get(3).getId().toString()); + assertEquals("V5", ((SingleValue) stage1Inserts.get(3).getValue()).toDatabaseString()); + + assertFalse(stage1Inserts.get(4) instanceof Replacement); + assertEquals("7", stage1Inserts.get(4).getId().toString()); + assertEquals("V7", ((SingleValue) stage1Inserts.get(4).getValue()).toDatabaseString()); + + assertTrue(stage1Inserts.get(5) instanceof Replacement); + assertEquals("-2", stage1Inserts.get(5).getId().toString()); + assertEquals("V8", ((SingleValue) stage1Inserts.get(5).getValue()).toDatabaseString()); + + assertTrue(stage1Inserts.get(6) instanceof Replacement); + assertEquals("-3", stage1Inserts.get(6).getId().toString()); + assertEquals("V9", ((SingleValue) stage1Inserts.get(6).getValue()).toDatabaseString()); + + assertEquals(6, stage2Inserts.size()); + assertEquals(new EntityID("3"), stage2Inserts.get(0).getId()); + assertEquals("V3", ((SingleValue) stage2Inserts.get(0).getValue()).toDatabaseString()); + assertEquals(new EntityID("2"), stage2Inserts.get(0).getDomain()); + + assertEquals(new EntityID("-1"), stage2Inserts.get(1).getId()); + assertEquals(ReplacementStatus.REPLACEMENT, stage2Inserts.get(1).getStatementStatus()); + assertEquals(new EntityID("5"), ((ReferenceValue) stage2Inserts.get(1).getValue()).getId()); + assertEquals(new EntityID("4"), stage2Inserts.get(1).getDomain()); + + assertEquals(new EntityID("6"), stage2Inserts.get(2).getId()); + assertEquals("V6", ((SingleValue) stage2Inserts.get(2).getValue()).toDatabaseString()); + assertEquals(new EntityID("-1"), stage2Inserts.get(2).getDomain()); + + assertEquals(new EntityID("-2"), stage2Inserts.get(3).getId()); + assertEquals(ReplacementStatus.REPLACEMENT, stage2Inserts.get(3).getStatementStatus()); + assertEquals(new EntityID("8"), ((ReferenceValue) stage2Inserts.get(3).getValue()).getId()); + assertEquals(new EntityID("7"), stage2Inserts.get(3).getDomain()); + + assertEquals(new EntityID("-3"), stage2Inserts.get(4).getId()); + assertEquals(ReplacementStatus.REPLACEMENT, stage2Inserts.get(4).getStatementStatus()); + assertEquals(new EntityID("9"), ((ReferenceValue) stage2Inserts.get(4).getValue()).getId()); + assertEquals(new EntityID("-2"), stage2Inserts.get(4).getDomain()); + + assertEquals(new EntityID("10"), stage2Inserts.get(5).getId()); + assertEquals("V10", ((SingleValue) stage2Inserts.get(5).getValue()).toDatabaseString()); + assertEquals(new EntityID("-3"), stage2Inserts.get(5).getDomain()); + } + + /** + * Record with two properties of the same id and different values. One has a sub property, the + * other does not. + * + * @throws Exception + */ + @Test + public void testTransformation2() throws Exception { + final Entity r = new InsertEntity("Test", Role.Record); + final Property p1 = new Property(new RetrieveEntity(new EntityID("1"))); + p1.setRole("Property"); + p1.setValue(new GenericValue("V1-1")); + p1.setDatatype("TEXT"); + p1.setStatementStatus(StatementStatus.FIX); + r.addProperty(p1); + + final Property p2 = new Property(new RetrieveEntity(new EntityID("1"))); + p2.setRole("Property"); + p2.setValue(new GenericValue("V1-2")); + p2.setDatatype("TEXT"); + p2.setStatementStatus(StatementStatus.FIX); + r.addProperty(p2); + + final Property subp = new Property(new RetrieveEntity(new EntityID("2"))); + subp.setRole("Property"); + subp.setValue(new GenericValue("V2")); + subp.setDatatype("TEXT"); + subp.setStatementStatus(StatementStatus.FIX); + p2.addProperty(subp); + + final LinkedList<Property> stage1Inserts = new LinkedList<>(); + final LinkedList<Property> stage2Inserts = new LinkedList<>(); + + int replacementCount = deriveStage1Inserts(stage1Inserts, r); + deriveStage2Inserts(stage2Inserts, stage1Inserts, registerReplacementIds(replacementCount), r); + + assertEquals(3, stage1Inserts.size()); + + assertFalse(stage1Inserts.get(0) instanceof Replacement); + assertEquals(new EntityID("1"), stage1Inserts.get(0).getId()); + assertEquals("V1-1", ((SingleValue) stage1Inserts.get(0).getValue()).toDatabaseString()); + + assertTrue(stage1Inserts.get(1) instanceof Replacement); + assertEquals("V1-2", ((SingleValue) stage1Inserts.get(1).getValue()).toDatabaseString()); + assertEquals(new EntityID("-1"), stage1Inserts.get(1).getId()); + + assertFalse(stage1Inserts.get(2) instanceof Replacement); + assertEquals(new EntityID("-1"), stage1Inserts.get(2).getId()); + assertEquals("1", ((SingleValue) stage1Inserts.get(2).getValue()).toDatabaseString()); + + assertEquals(1, stage2Inserts.size()); + + assertFalse(stage2Inserts.get(0) instanceof Replacement); + assertEquals(new EntityID("2"), stage2Inserts.get(0).getId()); + assertEquals("V2", ((SingleValue) stage2Inserts.get(0).getValue()).toDatabaseString()); + } + + /** + * Record with three properties of the same id without any value. The first has a sub property, + * the others do not. + * + * @throws Exception + */ + @Test + public void testTransformation3() throws Exception { + final Entity r = new InsertEntity("Test", Role.Record); + final Property p1 = new Property(new RetrieveEntity(new EntityID("1"))); + p1.setRole("Property"); + p1.setDatatype("TEXT"); + p1.setStatementStatus(StatementStatus.FIX); + r.addProperty(p1); + + final Property p2 = new Property(new RetrieveEntity(new EntityID("1"))); + p2.setRole("Property"); + p2.setDatatype("TEXT"); + p2.setStatementStatus(StatementStatus.FIX); + r.addProperty(p2); + + final Property p3 = new Property(new RetrieveEntity(new EntityID("1"))); + p3.setRole("Property"); + p3.setDatatype("TEXT"); + p3.setStatementStatus(StatementStatus.FIX); + r.addProperty(p3); + + final Property sub1 = new Property(new RetrieveEntity(new EntityID("2"))); + sub1.setRole("Property"); + sub1.setDatatype("TEXT"); + sub1.setValue(new GenericValue("V1")); + sub1.setStatementStatus(StatementStatus.FIX); + p1.addProperty(sub1); + + final LinkedList<Property> stage1Inserts = new LinkedList<>(); + final LinkedList<Property> stage2Inserts = new LinkedList<>(); + + int count = deriveStage1Inserts(stage1Inserts, r); + deriveStage2Inserts(stage2Inserts, stage1Inserts, registerReplacementIds(count), r); + + assertEquals(4, stage1Inserts.size()); + assertTrue(stage1Inserts.get(0) instanceof Replacement); + assertEquals(new EntityID("-1"), stage1Inserts.get(0).getId()); + assertEquals(null, stage1Inserts.get(0).getValue()); + + assertFalse(stage1Inserts.get(1) instanceof Replacement); + assertEquals(new EntityID("-1"), stage1Inserts.get(1).getId()); + assertEquals("1", ((ReferenceValue) stage1Inserts.get(1).getValue()).getId().toString()); + + assertFalse(stage1Inserts.get(2) instanceof Replacement); + assertEquals(new EntityID("1"), stage1Inserts.get(2).getId()); + assertEquals(null, stage1Inserts.get(2).getValue()); + + assertFalse(stage1Inserts.get(3) instanceof Replacement); + assertEquals(new EntityID("1"), stage1Inserts.get(3).getId()); + assertEquals(null, stage1Inserts.get(3).getValue()); + + assertEquals(1, stage2Inserts.size()); + assertEquals(new EntityID("2"), stage2Inserts.get(0).getId()); + assertEquals("V1", ((SingleValue) stage2Inserts.get(0).getValue()).toDatabaseString()); + assertEquals(new EntityID("-1"), stage2Inserts.get(0).getDomain()); + } + + /** + * Record with a list property. + * + * @throws Exception + */ + @Test + public void testTransformation4() throws Exception { + + final Entity r = new InsertEntity("Test", Role.Record); + final Property p1 = new Property(new RetrieveEntity(new EntityID("1"))); + p1.setRole("Property"); + p1.setDatatype("TEXT"); + p1.setValue(new GenericValue("V1")); + p1.setStatementStatus(StatementStatus.FIX); + r.addProperty(p1); + + final Property p2 = new Property(new RetrieveEntity(new EntityID("2"))); + p2.setRole("Property"); + p2.setDatatype("LIST<TEXT>"); + p2.setStatementStatus(StatementStatus.FIX); + final CollectionValue vals = new CollectionValue(); + vals.add(new GenericValue("L1")); + vals.add(new GenericValue("L2")); + vals.add(new GenericValue("L3")); + p2.setValue(vals); + r.addProperty(p2); + + final Property p3 = new Property(new RetrieveEntity(new EntityID("3"))); + p3.setRole("Property"); + p3.setDatatype("TEXT"); + p3.setValue(new GenericValue("V3")); + p3.setStatementStatus(StatementStatus.FIX); + r.addProperty(p3); + + final LinkedList<Property> stage1Inserts = new LinkedList<>(); + final LinkedList<Property> stage2Inserts = new LinkedList<>(); + + int count = deriveStage1Inserts(stage1Inserts, r); + deriveStage2Inserts(stage2Inserts, stage1Inserts, registerReplacementIds(count), r); + + assertEquals(4, stage1Inserts.size()); + + assertFalse(stage1Inserts.get(0) instanceof Replacement); + assertEquals(new EntityID("1"), stage1Inserts.get(0).getId()); + assertEquals("V1", ((SingleValue) stage1Inserts.get(0).getValue()).toDatabaseString()); + + assertTrue(stage1Inserts.get(1) instanceof Replacement); + assertEquals(new EntityID("-1"), stage1Inserts.get(1).getId()); + assertTrue(stage1Inserts.get(1).getValue() instanceof CollectionValue); + + assertFalse(stage1Inserts.get(2) instanceof Replacement); + assertEquals(new EntityID("-1"), stage1Inserts.get(2).getId()); + assertEquals("2", ((ReferenceValue) stage1Inserts.get(2).getValue()).getId().toString()); + + assertFalse(stage1Inserts.get(3) instanceof Replacement); + assertEquals(new EntityID("3"), stage1Inserts.get(3).getId()); + assertEquals("V3", ((SingleValue) stage1Inserts.get(3).getValue()).toDatabaseString()); + + assertEquals(0, stage2Inserts.size()); + } + + /** Deeply nested properties without any values, with overridden name and description */ + @Test + public void testTransformation5() { + final Entity r = new InsertEntity("Test", Role.RecordType); + final Property p1 = new Property(new RetrieveEntity(new EntityID("1"))); + p1.setRole("Property"); + p1.setDatatype("TEXT"); + p1.setDescription("desc1"); + p1.setDescOverride(true); + p1.setName("P1"); + p1.setNameOverride(true); + p1.setStatementStatus(StatementStatus.RECOMMENDED); + r.addProperty(p1); + + final Property p2 = new Property(new RetrieveEntity(new EntityID("2"))); + p2.setRole("Property"); + p2.setDatatype("TEXT"); + p2.setDescription("desc2"); + p2.setDescOverride(true); + p2.setName("P2"); + p2.setNameOverride(true); + p2.setStatementStatus(StatementStatus.RECOMMENDED); + r.addProperty(p2); + + final Property p21 = new Property(new RetrieveEntity(new EntityID("1"))); + p21.setRole("Property"); + p21.setDatatype("TEXT"); + p21.setDescription("desc21"); + p21.setDescOverride(true); + p21.setName("P21"); + p21.setNameOverride(true); + p21.setStatementStatus(StatementStatus.FIX); + p2.addProperty(p21); + + final Property p22 = new Property(new RetrieveEntity(new EntityID("2"))); + p22.setRole("Property"); + p22.setDatatype("TEXT"); + p22.setDescription("desc22"); + p22.setDescOverride(true); + p22.setName("P22"); + p22.setNameOverride(true); + p22.setStatementStatus(StatementStatus.FIX); + p2.addProperty(p22); + + final Property p3 = new Property(new RetrieveEntity(new EntityID("3"))); + p3.setRole("Property"); + p3.setDatatype("TEXT"); + p3.setDescription("desc3"); + p3.setDescOverride(true); + p3.setName("P3"); + p3.setNameOverride(true); + p3.setStatementStatus(StatementStatus.RECOMMENDED); + r.addProperty(p3); + + final Property p31 = new Property(new RetrieveEntity(new EntityID("1"))); + p31.setRole("Property"); + p31.setDatatype("TEXT"); + p31.setDescription("desc31"); + p31.setDescOverride(true); + p31.setName("P31"); + p31.setNameOverride(true); + p31.setStatementStatus(StatementStatus.FIX); + p3.addProperty(p31); + + final Property p32 = new Property(new RetrieveEntity(new EntityID("2"))); + p32.setRole("Property"); + p32.setDatatype("TEXT"); + p32.setDescription("desc32"); + p32.setDescOverride(true); + p32.setName("P32"); + p32.setNameOverride(true); + p32.setStatementStatus(StatementStatus.FIX); + p3.addProperty(p32); + + final Property p321 = new Property(new RetrieveEntity(new EntityID("1"))); + p321.setRole("Property"); + p321.setDatatype("TEXT"); + p321.setDescription("desc321"); + p321.setDescOverride(true); + p321.setName("P321"); + p321.setNameOverride(true); + p321.setStatementStatus(StatementStatus.FIX); + p32.addProperty(p321); + + final Property p322 = new Property(new RetrieveEntity(new EntityID("2"))); + p322.setRole("Property"); + p322.setDatatype("TEXT"); + p322.setDescription("desc322"); + p322.setDescOverride(true); + p322.setName("P322"); + p322.setNameOverride(true); + p322.setStatementStatus(StatementStatus.FIX); + p32.addProperty(p322); + + final Property p323 = new Property(new RetrieveEntity(new EntityID("3"))); + p323.setRole("Property"); + p323.setDatatype("TEXT"); + p323.setDescription("desc323"); + p323.setDescOverride(true); + p323.setName("P323"); + p323.setNameOverride(true); + p323.setStatementStatus(StatementStatus.FIX); + p32.addProperty(p323); + + final Property p33 = new Property(new RetrieveEntity(new EntityID("3"))); + p33.setRole("Property"); + p33.setDatatype("TEXT"); + p33.setDescription("desc33"); + p33.setDescOverride(true); + p33.setName("P33"); + p33.setNameOverride(true); + p33.setStatementStatus(StatementStatus.FIX); + p3.addProperty(p33); + + List<Property> stage1Inserts = new LinkedList<>(); + List<Property> stage2Inserts = new LinkedList<>(); + int c = DatabaseUtils.deriveStage1Inserts(stage1Inserts, r); + DatabaseUtils.deriveStage2Inserts(stage2Inserts, stage1Inserts, registerReplacementIds(c), r); + + assertEquals(4, c); + assertEquals(7, stage1Inserts.size()); + assertEquals(8, stage2Inserts.size()); + } +} diff --git a/src/test/java/org/caosdb/server/database/backend/transaction/BackendTransactionTest.java b/src/test/java/org/caosdb/server/database/backend/transaction/BackendTransactionTest.java index e0cb425a25ed4d3c27e1cdbb610607077ed0fb92..0ea5f871925524bba650cc47a63d717e6b2f8437 100644 --- a/src/test/java/org/caosdb/server/database/backend/transaction/BackendTransactionTest.java +++ b/src/test/java/org/caosdb/server/database/backend/transaction/BackendTransactionTest.java @@ -19,7 +19,7 @@ public class BackendTransactionTest { final BackendTransaction transaction = new InsertTransactionHistory(null, null, null, null); assertEquals("Retrieve", transaction.getTransactionType(new RetrieveEntity("test"))); assertEquals("Insert", transaction.getTransactionType(new InsertEntity("test", Role.Record))); - assertEquals("Delete", transaction.getTransactionType(new DeleteEntity(new EntityID(1234)))); + assertEquals("Delete", transaction.getTransactionType(new DeleteEntity(new EntityID("1234")))); assertEquals("Update", transaction.getTransactionType(new UpdateEntity(new Element("Record")))); } } diff --git a/src/test/java/org/caosdb/server/database/backend/transaction/RetrieveFullEntityTest.java b/src/test/java/org/caosdb/server/database/backend/transaction/RetrieveFullEntityTest.java index 72c84d1f7b262a854a257c8ae764d4e3da9b8e80..bb209f0eb5ea2a7438b7488955cb1a1d22424ca5 100644 --- a/src/test/java/org/caosdb/server/database/backend/transaction/RetrieveFullEntityTest.java +++ b/src/test/java/org/caosdb/server/database/backend/transaction/RetrieveFullEntityTest.java @@ -1,9 +1,8 @@ /* - * ** header v3.0 * This file is a part of the CaosDB Project. * - * Copyright (C) 2020 Timm Fitschen <t.fitschen@indiscale.com> - * Copyright (C) 2020 IndiScale GmbH <info@indiscale.com> + * Copyright (C) 2020,2023 Timm Fitschen <t.fitschen@indiscale.com> + * Copyright (C) 2020,2023 IndiScale GmbH <info@indiscale.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -17,8 +16,6 @@ * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. - * - * ** end header */ package org.caosdb.server.database.backend.transaction; @@ -26,8 +23,21 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; +import java.io.IOException; import java.util.ArrayList; +import java.util.Collection; import java.util.List; +import java.util.concurrent.Callable; +import org.apache.shiro.authc.AuthenticationException; +import org.apache.shiro.authc.AuthenticationToken; +import org.apache.shiro.authz.AuthorizationException; +import org.apache.shiro.authz.Permission; +import org.apache.shiro.session.Session; +import org.apache.shiro.subject.ExecutionException; +import org.apache.shiro.subject.PrincipalCollection; +import org.apache.shiro.subject.Subject; +import org.caosdb.server.CaosDBServer; +import org.caosdb.server.accessControl.Principal; import org.caosdb.server.datatype.ReferenceValue; import org.caosdb.server.entity.Entity; import org.caosdb.server.entity.EntityID; @@ -35,40 +45,195 @@ import org.caosdb.server.entity.EntityInterface; import org.caosdb.server.entity.RetrieveEntity; import org.caosdb.server.entity.wrapper.Property; import org.caosdb.server.entity.xml.PropertyToElementStrategyTest; +import org.caosdb.server.permissions.EntityACL; import org.caosdb.server.query.Query.Selection; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; public class RetrieveFullEntityTest { + @BeforeAll + public static void setup() throws IOException { + CaosDBServer.initServerProperties(); + } + @Test public void testRetrieveSubEntities() { final RetrieveFullEntityTransaction r = - new RetrieveFullEntityTransaction(new EntityID(0)) { + new RetrieveFullEntityTransaction( + new EntityID("0"), + new Subject() { + + @Override + public Object getPrincipal() { + return new Principal("Bla", "Blub"); + } + + @Override + public PrincipalCollection getPrincipals() { + return null; + } + + @Override + public boolean isPermitted(String permission) { + assertEquals( + permission, org.caosdb.server.permissions.EntityPermission.RETRIEVE_ENTITY); + return true; + } + + @Override + public boolean isPermitted(Permission permission) { + return false; + } + + @Override + public boolean[] isPermitted(String... permissions) { + return null; + } + + @Override + public boolean[] isPermitted(List<Permission> permissions) { + return null; + } + + @Override + public boolean isPermittedAll(String... permissions) { + return false; + } + + @Override + public boolean isPermittedAll(Collection<Permission> permissions) { + return false; + } + + @Override + public void checkPermission(String permission) throws AuthorizationException {} + + @Override + public void checkPermission(Permission permission) throws AuthorizationException {} + + @Override + public void checkPermissions(String... permissions) throws AuthorizationException {} + + @Override + public void checkPermissions(Collection<Permission> permissions) + throws AuthorizationException {} + + @Override + public boolean hasRole(String roleIdentifier) { + return false; + } + + @Override + public boolean[] hasRoles(List<String> roleIdentifiers) { + return null; + } + + @Override + public boolean hasAllRoles(Collection<String> roleIdentifiers) { + return false; + } + + @Override + public void checkRole(String roleIdentifier) throws AuthorizationException {} + + @Override + public void checkRoles(Collection<String> roleIdentifiers) + throws AuthorizationException {} + + @Override + public void checkRoles(String... roleIdentifiers) throws AuthorizationException {} + + @Override + public void login(AuthenticationToken token) throws AuthenticationException {} + + @Override + public boolean isAuthenticated() { + return false; + } + + @Override + public boolean isRemembered() { + return false; + } + + @Override + public Session getSession() { + return null; + } + + @Override + public Session getSession(boolean create) { + return null; + } + + @Override + public void logout() {} + + @Override + public <V> V execute(Callable<V> callable) throws ExecutionException { + return null; + } + + @Override + public void execute(Runnable runnable) {} + + @Override + public <V> Callable<V> associateWith(Callable<V> callable) { + return null; + } + + @Override + public Runnable associateWith(Runnable runnable) { + return null; + } + + @Override + public void runAs(PrincipalCollection principals) + throws NullPointerException, IllegalStateException {} + + @Override + public boolean isRunAs() { + return false; + } + + @Override + public PrincipalCollection getPreviousPrincipals() { + return null; + } + + @Override + public PrincipalCollection releaseRunAs() { + return null; + } + }) { /** Mock-up */ @Override public void retrieveFullEntity( final EntityInterface e, final List<Selection> selections) { // The id of the referenced window - assertEquals(new EntityID(1234), e.getId()); + assertEquals(new EntityID("1234"), e.getId()); // The level of selectors has been reduced by 1 assertEquals("description", selections.get(0).getSelector()); e.setDescription("A heart-shaped window."); - }; + e.setEntityACL(new EntityACL()); + } + ; }; - final Property window = new Property(new RetrieveEntity(new EntityID(2345))); + final Property window = new Property(new RetrieveEntity(new EntityID("2345"))); window.setName("Window"); window.setDatatype("Window"); - window.setValue(new ReferenceValue(1234)); + window.setValue(new ReferenceValue("1234")); - final Entity house = new RetrieveEntity(new EntityID(3456)); + final Entity house = new RetrieveEntity(new EntityID("3456")); house.addProperty(window); final ReferenceValue value = - (ReferenceValue) house.getProperties().getEntityById(new EntityID(2345)).getValue(); - assertEquals(new EntityID(1234), value.getId()); + (ReferenceValue) house.getProperties().getEntityById(new EntityID("2345")).getValue(); + assertEquals(new EntityID("1234"), value.getId()); assertNull(value.getEntity()); final List<Selection> selections = new ArrayList<>(); @@ -76,7 +241,7 @@ public class RetrieveFullEntityTest { r.retrieveSubEntities(house, selections); - assertEquals(new EntityID(1234), value.getId()); + assertEquals(new EntityID("1234"), value.getId()); assertNotNull(value.getEntity()); assertEquals("A heart-shaped window.", value.getEntity().getDescription()); } diff --git a/src/test/java/org/caosdb/server/entity/container/PropertyContainerTest.java b/src/test/java/org/caosdb/server/entity/container/PropertyContainerTest.java index a8ccdbdc9f7a9522020332e01cba96a1667d70b6..f8fb0217adb0cc35cbc51d90b96f76d5f7b3592e 100644 --- a/src/test/java/org/caosdb/server/entity/container/PropertyContainerTest.java +++ b/src/test/java/org/caosdb/server/entity/container/PropertyContainerTest.java @@ -1,9 +1,8 @@ /* - * ** header v3.0 * This file is a part of the CaosDB Project. * - * Copyright (C) 2020 Timm Fitschen <t.fitschen@indiscale.com> - * Copyright (C) 2020 IndiScale GmbH <info@indiscale.com> + * Copyright (C) 2020,2023 Timm Fitschen <t.fitschen@indiscale.com> + * Copyright (C) 2020,2023 IndiScale GmbH <info@indiscale.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -17,8 +16,6 @@ * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. - * - * ** end header */ package org.caosdb.server.entity.container; @@ -48,7 +45,7 @@ public class PropertyContainerTest { @BeforeAll public static void setup() { - window = new RetrieveEntity(new EntityID(1234)); + window = new RetrieveEntity(new EntityID("1234")); windowHeight = new Property(new RetrieveEntity("window.height", Role.Property)); window.addProperty(windowHeight); windowHeight.setValue(new GenericValue("windowHeight")); @@ -59,7 +56,7 @@ public class PropertyContainerTest { houseHeight = new Property(new RetrieveEntity("height", Role.Property)); houseHeight.setValue(new GenericValue("houseHeight")); house.addProperty(houseHeight); - windowProperty = new Property(new RetrieveEntity(new EntityID(2345))); + windowProperty = new Property(new RetrieveEntity(new EntityID("2345"))); windowProperty.setName("window"); windowProperty.setValue(new ReferenceValue(window.getId())); house.addProperty(windowProperty); diff --git a/src/test/java/org/caosdb/server/entity/xml/PropertyToElementStrategyTest.java b/src/test/java/org/caosdb/server/entity/xml/PropertyToElementStrategyTest.java index a2c9676b9759fc55fb0d1e089808ab50d016288e..bab174c88ed595ec4b79b1cf47b1cf2dbb0cbc95 100644 --- a/src/test/java/org/caosdb/server/entity/xml/PropertyToElementStrategyTest.java +++ b/src/test/java/org/caosdb/server/entity/xml/PropertyToElementStrategyTest.java @@ -1,9 +1,8 @@ /* - * ** header v3.0 * This file is a part of the CaosDB Project. * - * Copyright (C) 2020 Timm Fitschen <t.fitschen@indiscale.com> - * Copyright (C) 2020 IndiScale GmbH <info@indiscale.com> + * Copyright (C) 2020,2023 Timm Fitschen <t.fitschen@indiscale.com> + * Copyright (C) 2020,2023 IndiScale GmbH <info@indiscale.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -17,8 +16,6 @@ * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. - * - * ** end header */ package org.caosdb.server.entity.xml; @@ -66,7 +63,7 @@ public class PropertyToElementStrategyTest { @BeforeEach public void setup() { - window = new RetrieveEntity(new EntityID(1234), Role.Record); + window = new RetrieveEntity(new EntityID("1234"), Role.Record); windowHeight = new Property(new RetrieveEntity("height", Role.Property)); window.addProperty(windowHeight); windowHeight.setValue(new GenericValue("windowHeight")); @@ -77,7 +74,7 @@ public class PropertyToElementStrategyTest { houseHeight = new Property(new RetrieveEntity("height", Role.Property)); houseHeight.setValue(new GenericValue("houseHeight")); house.addProperty(houseHeight); - windowProperty = new Property(new RetrieveEntity(new EntityID(2345))); + windowProperty = new Property(new RetrieveEntity(new EntityID("2345"))); windowProperty.setName("window"); windowProperty.setDatatype("window"); windowProperty.setValue(new ReferenceValue(window.getId())); diff --git a/src/test/java/org/caosdb/server/grpc/CaosDBToGrpcConvertersTest.java b/src/test/java/org/caosdb/server/grpc/CaosDBToGrpcConvertersTest.java index 0a4240d61e7084030722dbe8566acb73f5fcd5ee..2756a39447ab606d6619b2cc5e35f310950fee17 100644 --- a/src/test/java/org/caosdb/server/grpc/CaosDBToGrpcConvertersTest.java +++ b/src/test/java/org/caosdb/server/grpc/CaosDBToGrpcConvertersTest.java @@ -1,12 +1,33 @@ +/* + * This file is a part of the CaosDB Project. + * + * Copyright (C) 2023 IndiScale GmbH <info@indiscale.com> + * Copyright (C) 2023 Timm Fitschen <t.fitschen@indiscale.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ package org.caosdb.server.grpc; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.TimeZone; +import org.caosdb.api.entity.v1.Value.Builder; import org.caosdb.datetime.DateTimeFactory2; +import org.caosdb.server.datatype.CollectionValue; import org.caosdb.server.datatype.GenericValue; +import org.caosdb.server.datatype.ReferenceValue; import org.caosdb.server.datatype.Value; import org.caosdb.server.entity.EntityID; import org.caosdb.server.entity.Message; @@ -20,6 +41,7 @@ import org.caosdb.server.filesystem.FSODescriptor; import org.caosdb.server.filesystem.Hash; import org.caosdb.server.filesystem.Path; import org.caosdb.server.filesystem.VirtualFSODescriptorInterface; +import org.caosdb.server.query.Query.Selection; import org.junit.jupiter.api.Test; public class CaosDBToGrpcConvertersTest { @@ -30,7 +52,9 @@ public class CaosDBToGrpcConvertersTest { DateTimeFactory2 factory = new DateTimeFactory2(timeZone); CaosDBToGrpcConverters converters = new CaosDBToGrpcConverters(timeZone); Value value = null; - assertNull(converters.convertScalarValue(value)); + assertEquals( + converters.convertScalarValue(value).toString(), + "special_value: SPECIAL_VALUE_UNSPECIFIED\n"); value = factory.parse("2022"); assertEquals(converters.convertScalarValue(value).toString(), "string_value: \"2022\"\n"); value = factory.parse("2022-12"); @@ -74,7 +98,7 @@ public class CaosDBToGrpcConvertersTest { RetrieveEntity entity = new RetrieveEntity(); // must be printed - entity.setId(new EntityID(1234)); + entity.setId(new EntityID("1234")); entity.addInfo("info"); entity.addWarning(new Message("warning")); entity.addError(new Message("error")); @@ -109,4 +133,68 @@ public class CaosDBToGrpcConvertersTest { converters.convert(entity).toString(), "entity {\n id: \"1234\"\n}\nerrors {\n code: 1\n description: \"error\"\n}\nwarnings {\n code: 1\n description: \"warning\"\n}\ninfos {\n code: 1\n description: \"info\"\n}\n"); } + + @Test + public void testGetSelectedValueWithNullValue() { + Property p = new Property(new RetrieveEntity()); + p.setName("p0"); + p.setDatatype("DOUBLE"); + RetrieveEntity entity = new RetrieveEntity(); + entity.addProperty(p); + + CaosDBToGrpcConverters converters = new CaosDBToGrpcConverters(null); + Builder value = converters.getSelectedValue(new Selection("p0"), entity); + assertEquals( + "scalar_value {\n" + " special_value: SPECIAL_VALUE_UNSPECIFIED\n" + "}\n", + value.toString()); + } + + @Test + public void testGetSelectedValueWithListOfReferenceValue() { + CollectionValue col = new CollectionValue(); + Property p = new Property(new RetrieveEntity()); + p.setName("Person"); + p.setDatatype("List<Person>"); + + Property fullName1 = new Property(new RetrieveEntity()); + fullName1.setName("full name"); + fullName1.setDatatype("TEXT"); + fullName1.setValue(new GenericValue("Harry Belafonte")); + + RetrieveEntity person1 = new RetrieveEntity(); + person1.addProperty(fullName1); + ReferenceValue val1 = new ReferenceValue(new EntityID("1234")); + val1.setEntity(person1, false); + col.add(val1); + + Property fullName2 = new Property(new RetrieveEntity()); + fullName2.setName("full name"); + fullName2.setDatatype("TEXT"); + fullName2.setValue(new GenericValue("Louis Prima")); + + RetrieveEntity person2 = new RetrieveEntity(); + person2.addProperty(fullName2); + ReferenceValue val2 = new ReferenceValue(new EntityID("1234")); + val2.setEntity(person2, false); + col.add(val2); + p.setValue(col); + + RetrieveEntity entity = new RetrieveEntity(); + entity.addProperty(p); + + CaosDBToGrpcConverters converters = new CaosDBToGrpcConverters(null); + Builder value = + converters.getSelectedValue( + new Selection("Person").setSubSelection(new Selection("full name")), entity); + assertEquals( + "list_values {\n" + + " values {\n" + + " string_value: \"Harry Belafonte\"\n" + + " }\n" + + " values {\n" + + " string_value: \"Louis Prima\"\n" + + " }\n" + + "}\n", + value.toString()); + } } diff --git a/src/test/java/org/caosdb/server/jobs/JobConfigTest.java b/src/test/java/org/caosdb/server/jobs/JobConfigTest.java index e176c2111189fb52ca9946e10ae5ec705549b6c8..c097d0a76ec5444881e9dd58bb735015b9fe8db1 100644 --- a/src/test/java/org/caosdb/server/jobs/JobConfigTest.java +++ b/src/test/java/org/caosdb/server/jobs/JobConfigTest.java @@ -1,3 +1,22 @@ +/* + * This file is a part of the CaosDB Project. + * + * Copyright (C) 2023 IndiScale GmbH <info@indiscale.com> + * Copyright (C) 2023 Timm Fitschen <t.fitschen@indiscale.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ package org.caosdb.server.jobs; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -26,7 +45,7 @@ public class JobConfigTest { final JobConfig jobConfig = JobConfig.getInstance(); assertEquals("Retrieve", jobConfig.getTransactionType(new RetrieveEntity("test"))); assertEquals("Insert", jobConfig.getTransactionType(new InsertEntity("test", Role.Record))); - assertEquals("Delete", jobConfig.getTransactionType(new DeleteEntity(new EntityID(1234)))); + assertEquals("Delete", jobConfig.getTransactionType(new DeleteEntity(new EntityID("1234")))); assertEquals("Update", jobConfig.getTransactionType(new UpdateEntity(new Element("Record")))); } } diff --git a/src/test/java/org/caosdb/server/query/POVTest.java b/src/test/java/org/caosdb/server/query/POVTest.java new file mode 100644 index 0000000000000000000000000000000000000000..7212423bbaf2ac4d3c5e33543d3e6ffe4e70b5e4 --- /dev/null +++ b/src/test/java/org/caosdb/server/query/POVTest.java @@ -0,0 +1,77 @@ +package org.caosdb.server.query; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.regex.Matcher; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class POVTest { + + public static final String MAX_INT = "2147483647"; + public static final String MIN_INT = "-2147483648"; + public static final String MAX_DOUBLE = "1.7976931348623157E308"; + public static final String MIN_DOUBLE = "4.9E-324"; + + @ParameterizedTest + @ValueSource(strings = {"16", MAX_INT, MIN_INT, "0", "-0", "1", "- 1", "-1", "+1"}) + void testNumberPatternMatchInteger(String intValue) { + + Matcher matcher = POV.NUMBER_PATTERN.matcher(intValue); + assertTrue(matcher.matches()); + assertEquals(intValue, matcher.group(1).toString()); + + Integer.valueOf(intValue.replaceAll("\\s", "")); + + Matcher matcherWithUnit = POV.NUMBER_PATTERN.matcher(intValue + " m^2"); + assertTrue(matcherWithUnit.matches()); + assertEquals(intValue, matcherWithUnit.group(1).toString()); + assertEquals("m^2", matcherWithUnit.group(2).toString()); + + Matcher matcherWithStrangeUnit = POV.NUMBER_PATTERN.matcher(intValue + " e"); + assertTrue(matcherWithStrangeUnit.matches()); + assertEquals(intValue, matcherWithStrangeUnit.group(1).toString()); + assertEquals("e", matcherWithStrangeUnit.group(2).toString()); + } + + @ParameterizedTest + @ValueSource( + strings = { + "1.2123e+3", + "1.21234E+3", + "5.213e2", + "5.2234E2", + "16.0", + MAX_DOUBLE, + MIN_DOUBLE, + "0.0", + "-0.0", + "1.2", + "- 1.2", + "-1.2", + "2e-323", + "2E-323", + "2E- 323", + "2 e -323", + "+ 2.2132e+23" + }) + void testNumberPatternMatchDouble(String doubleValue) { + + Matcher matcher = POV.NUMBER_PATTERN.matcher(doubleValue); + assertTrue(matcher.matches()); + assertEquals(doubleValue, matcher.group(1).toString()); + + Double.valueOf(doubleValue.replaceAll("\\s", "")); + + Matcher matcherWithUnit = POV.NUMBER_PATTERN.matcher(doubleValue + " m^2"); + assertTrue(matcherWithUnit.matches()); + assertEquals(doubleValue, matcherWithUnit.group(1).toString()); + assertEquals("m^2", matcherWithUnit.group(2).toString()); + + Matcher matcherWithStrangeUnit = POV.NUMBER_PATTERN.matcher(doubleValue + " e"); + assertTrue(matcherWithStrangeUnit.matches()); + assertEquals(doubleValue, matcherWithStrangeUnit.group(1).toString()); + assertEquals("e", matcherWithStrangeUnit.group(2).toString()); + } +} diff --git a/src/test/java/org/caosdb/server/query/TestCQL.java b/src/test/java/org/caosdb/server/query/TestCQL.java index fe73e2b2c33749fac6d72a4c5b478eca5d154465..9ca5904a5b851a4a32de5f2328647b64fa05cfec 100644 --- a/src/test/java/org/caosdb/server/query/TestCQL.java +++ b/src/test/java/org/caosdb/server/query/TestCQL.java @@ -25,6 +25,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrowsExactly; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; @@ -35,17 +36,18 @@ import java.util.regex.Matcher; import org.antlr.v4.runtime.CharStreams; import org.antlr.v4.runtime.CommonTokenStream; import org.antlr.v4.runtime.Token; +import org.antlr.v4.runtime.Vocabulary; import org.antlr.v4.runtime.tree.ParseTree; import org.caosdb.server.CaosDBServer; -import org.caosdb.server.database.access.Access; import org.caosdb.server.database.backend.implementation.MySQL.ConnectionException; import org.caosdb.server.database.exceptions.TransactionException; import org.caosdb.server.query.CQLParser.CqContext; import org.caosdb.server.query.Query.Pattern; import org.caosdb.server.query.Query.QueryException; -import org.caosdb.server.utils.Initialization; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; public class TestCQL { @@ -54,8 +56,6 @@ public class TestCQL { CaosDBServer.initServerProperties(); } - static Access access = Initialization.setUp().getAccess(); - String query1 = "FIND ename.pname1=val1"; String query1a = "FIND ename WITH pname1=val1"; String query1b = "FIND ename WHICH HAS A PROPERTY pname1=val1"; @@ -272,6 +272,7 @@ public class TestCQL { String issue131d = "FIND ename WITH (pname1.x) AND pname2"; String issue131e = "FIND ename WITH (pname1.pname2 > 30) AND (pname1.pname2 < 40)"; String issue131f = "FIND ename WITH (pname1.pname2 > 30) AND pname1.pname2 < 40"; + String issue144 = "FIND ename WITH pname = "; // https://gitlab.com/caosdb/caosdb-server/-/issues/130 String issue130a = @@ -281,6 +282,8 @@ public class TestCQL { // quotation marks gone rogue String quotation1 = "FIND ENTITY WHICH HAS A PROPERTY LIKE '*with double*' AND A PROPERTY LIKE '*and single*' AND A PROPERTY LIKE '*what\\'s wrong?*' AND A PROPERTY LIKE '*\\'*' AND A PROPERTY LIKE '*nothin\\'*' AND A PROPERTY LIKE '*\"\\'bla*'"; + String issue203a = "FIND WHICH ( HAS pname )"; + String issue203b = "FIND WHICH ( HAS pname1 AND REFERENCES pname2 )"; @Test public void testQuery1() @@ -3540,7 +3543,10 @@ public class TestCQL { */ @Test public void TestTicket147i() - throws InterruptedException, SQLException, ConnectionException, QueryException, + throws InterruptedException, + SQLException, + ConnectionException, + QueryException, TransactionException { CQLLexer lexer; lexer = new CQLLexer(CharStreams.fromString(this.ticket147i)); @@ -3558,7 +3564,7 @@ public class TestCQL { final Query query = new Query(this.ticket147i); try { - query.execute(access); + query.execute(); fail("This should throw a QueryException!"); } catch (final QueryException q) { } @@ -6748,6 +6754,7 @@ public class TestCQL { // must not throw ParsingException new Query(this.queryIssue134).parse(); } + /** * Space before special character unit * @@ -7013,4 +7020,114 @@ public class TestCQL { assertEquals("POV(null,LIKE ,%nothin'%)", conj.getFilters().get(4).toString()); assertEquals("POV(null,LIKE ,%\"'bla%)", conj.getFilters().get(5).toString()); } + + @ParameterizedTest + @ValueSource( + strings = { + "1e+23", + "1E+23", + "5e22", + "5E22", + "2e-323", + "2E-323", + "-123", + "-1e23", + "3E15m^2", + "-3e15m", + "-3e15 1/s", + "3e15 m^2", + "+1", + "+2.234", + "+2.234e+23", + "+3.324E-23" + }) + public void testIssue144(String scientific_notation) { + CQLLexer lexer; + lexer = new CQLLexer(CharStreams.fromString(this.issue144 + scientific_notation)); + final CommonTokenStream tokens = new CommonTokenStream(lexer); + + final CQLParser parser = new CQLParser(tokens); + final CqContext sfq = parser.cq(); + + for (final Token t : tokens.getTokens()) { + System.out.println(t.toString()); + } + + System.out.println(sfq.toStringTree(parser)); + + assertTrue(sfq.filter instanceof POV); + POV pov = (POV) sfq.filter; + assertEquals("POV(pname,=," + scientific_notation + ")", pov.toString()); + assertTrue(pov.getVDouble() != null || pov.getVInt() != null); + } + + @ParameterizedTest + @ValueSource(strings = {"- 123", "- 1e23", "2 e -23", "2E- 323", "+ 1"}) + public void testIssue144WhiteSpaceInNumber(String number) { + CQLLexer lexer; + lexer = new CQLLexer(CharStreams.fromString(this.issue144 + number)); + final CommonTokenStream tokens = new CommonTokenStream(lexer); + + final CQLParser parser = new CQLParser(tokens); + assertThrowsExactly(Query.ParsingException.class, () -> parser.cq()); + } + + /** + * Test that brackets around 'has pname' do not cause filter to become subproperty filter. + * + * <p>https://gitlab.com/caosdb/caosdb-server/-/issues/203 + */ + @Test + public void testIssue203() { + // setup 203a + CQLLexer lexer; + lexer = new CQLLexer(CharStreams.fromString(this.issue203a)); + CommonTokenStream tokens = new CommonTokenStream(lexer); + CQLParser parser = new CQLParser(tokens); + CqContext sfq = parser.cq(); + + // here we test that the filter stays a POV and is not falsly interpreted + // as subproperty filter + assertTrue(sfq.filter instanceof POV); + + // setup 203b (with conjunction) + lexer = new CQLLexer(CharStreams.fromString(this.issue203b)); + tokens = new CommonTokenStream(lexer); + parser = new CQLParser(tokens); + sfq = parser.cq(); + Conjunction conj = (Conjunction) sfq.filter; + + // the outer filter should be conjuction + assertTrue(sfq.filter instanceof Conjunction); + // here we test that the filters stays a POV and is not falsly interpreted + // as subproperty filters + assertTrue(conj.getFilters().get(0) instanceof POV); + assertTrue(conj.getFilters().get(1) instanceof POV); + } + + @Test + public void testDecimalNumber() { + // This should always be DEC, WHITESPACE, AND repeat + final String text = + "1.2123e+3 AND 1.21234E+3 AND 1.21234E-3 AND 1.21234E3 AND 16.0 AND 1.2 AND -1.2 AND +1.2 AND 1.2 AND - 1.2 AND + 1.2 AND 2e-323 AND 2E-323 AND 2E- 323 AND 2 e -323 AND + 1.2123e+323"; + CQLLexer lexer = new CQLLexer(CharStreams.fromString(text)); + final CommonTokenStream tokens = new CommonTokenStream(lexer); + Vocabulary vocab = lexer.getVocabulary(); + + int no = 0; + for (final Token t : tokens.getTokens()) { + if (no % 3 == 0) { + assertEquals(vocab.getSymbolicName(t.getType()), "DECIMAL_NUMBER"); + } + if (no % 3 == 1) { + if (vocab.getSymbolicName(t.getType()) != "EOF") { + assertEquals(vocab.getSymbolicName(t.getType()), "WHITE_SPACE"); + } + } + if (no % 3 == 2) { + assertEquals(vocab.getSymbolicName(t.getType()), "AND"); + } + no = no + 1; + } + } } diff --git a/src/test/java/org/caosdb/server/resource/TestAbstractCaosDBServerResource.java b/src/test/java/org/caosdb/server/resource/TestAbstractCaosDBServerResource.java index ca4fbb0954f469055c2da3d0bf00b03cdc9dd563..4da4ba5cb4959787f5aaf1bf12e47140fdf4ee00 100644 --- a/src/test/java/org/caosdb/server/resource/TestAbstractCaosDBServerResource.java +++ b/src/test/java/org/caosdb/server/resource/TestAbstractCaosDBServerResource.java @@ -95,8 +95,12 @@ public class TestAbstractCaosDBServerResource { @Override protected Representation httpGetInChildClass() - throws ConnectionException, IOException, SQLException, CaosDBException, - NoSuchAlgorithmException, Exception { + throws ConnectionException, + IOException, + SQLException, + CaosDBException, + NoSuchAlgorithmException, + Exception { // TODO Auto-generated method stub return null; } diff --git a/src/test/java/org/caosdb/server/resource/TestScriptingResource.java b/src/test/java/org/caosdb/server/resource/TestScriptingResource.java index df145a72a4d407ab02dc1af02baf6b26ea2aeb97..0005bf343fc76e57add47611caee02d12ca4a44d 100644 --- a/src/test/java/org/caosdb/server/resource/TestScriptingResource.java +++ b/src/test/java/org/caosdb/server/resource/TestScriptingResource.java @@ -180,12 +180,12 @@ public class TestScriptingResource { return 0; } return -1; - }; + } @Override public Element generateRootElement(ServerSideScriptingCaller caller) { return new Element("OK"); - }; + } @Override public Object generateAuthToken(String purpose) { diff --git a/src/test/java/org/caosdb/server/transaction/RetrieveTest.java b/src/test/java/org/caosdb/server/transaction/RetrieveTest.java index a3ac40e1bf1e0fe6489a74f45d6c253b903b4478..95a019d46d9325759a3ad032ec285e98c9e51210 100644 --- a/src/test/java/org/caosdb/server/transaction/RetrieveTest.java +++ b/src/test/java/org/caosdb/server/transaction/RetrieveTest.java @@ -1,3 +1,22 @@ +/* + * This file is a part of the CaosDB Project. + * + * Copyright (C) 2023 IndiScale GmbH <info@indiscale.com> + * Copyright (C) 2023 Timm Fitschen <t.fitschen@indiscale.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ package org.caosdb.server.transaction; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -62,7 +81,7 @@ public class RetrieveTest { public void testMissingRetrievePermission() { Subject subject = SecurityUtils.getSubject(); subject.login(AnonymousAuthenticationToken.getInstance()); - EntityInterface entity = new RetrieveEntity(new EntityID(1234)); + EntityInterface entity = new RetrieveEntity(new EntityID("1234")); EntityACLFactory fac = new EntityACLFactory(); fac.deny(AnonymousAuthenticationToken.PRINCIPAL, "RETRIEVE:ENTITY"); entity.setEntityACL(fac.create()); diff --git a/src/test/java/org/caosdb/server/transaction/UpdateTest.java b/src/test/java/org/caosdb/server/transaction/UpdateTest.java index a0891a3eb34e1759100c78916d0fb250de149ef8..9a138106f4313f06ac2457728e3b5bd218470fa5 100644 --- a/src/test/java/org/caosdb/server/transaction/UpdateTest.java +++ b/src/test/java/org/caosdb/server/transaction/UpdateTest.java @@ -3,8 +3,9 @@ * * Copyright (C) 2018 Research Group Biomedical Physics, * Max-Planck-Institute for Dynamics and Self-Organization Göttingen - * Copyright (C) 2022 Indiscale GmbH <info@indiscale.com> + * Copyright (C) 2022,2023 Indiscale GmbH <info@indiscale.com> * Copyright (C) 2022 Daniel Hornung <d.hornung@indiscale.com> + * Copyright (C) 2023 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 @@ -75,11 +76,11 @@ public class UpdateTest { @Test public void testDeriveUpdate_SameProperty() throws NoSuchAlgorithmException, IOException, CaosDBException { - final Entity newEntity = new RetrieveEntity(new EntityID(1234)); - final Property newProperty = new Property(new RetrieveEntity(new EntityID(1))); + final Entity newEntity = new RetrieveEntity(new EntityID("1234")); + final Property newProperty = new Property(new RetrieveEntity(new EntityID("1"))); newEntity.addProperty(newProperty); - final Property oldProperty = new Property(new RetrieveEntity(new EntityID(1))); - final Entity oldEntity = new RetrieveEntity(new EntityID(1234)); + final Property oldProperty = new Property(new RetrieveEntity(new EntityID("1"))); + final Entity oldEntity = new RetrieveEntity(new EntityID("1234")); oldEntity.addProperty(oldProperty); new WriteTransaction(null).deriveUpdate(newEntity, oldEntity); @@ -89,13 +90,13 @@ public class UpdateTest { @Test public void testDeriveUpdate_AnotherProperty() throws NoSuchAlgorithmException, IOException, CaosDBException { - final Entity newEntity = new RetrieveEntity(new EntityID(1234)); - final Property newProperty = new Property(new RetrieveEntity(new EntityID(1))); - final Property newProperty2 = new Property(new RetrieveEntity(new EntityID(2))); + final Entity newEntity = new RetrieveEntity(new EntityID("1234")); + final Property newProperty = new Property(new RetrieveEntity(new EntityID("1"))); + final Property newProperty2 = new Property(new RetrieveEntity(new EntityID("2"))); newEntity.addProperty(newProperty); newEntity.addProperty(newProperty2); - final Property oldProperty = new Property(new RetrieveEntity(new EntityID(1))); - final Entity oldEntity = new RetrieveEntity(new EntityID(1234)); + final Property oldProperty = new Property(new RetrieveEntity(new EntityID("1"))); + final Entity oldEntity = new RetrieveEntity(new EntityID("1234")); oldEntity.addProperty(oldProperty); new WriteTransaction(null).deriveUpdate(newEntity, oldEntity); @@ -107,15 +108,15 @@ public class UpdateTest { @Test public void testDeriveUpdate_SameUnit() throws NoSuchAlgorithmException, IOException, CaosDBException { - final EntityInterface magicUnit = new RetrieveEntity(new EntityID(1234)); + final EntityInterface magicUnit = new RetrieveEntity(new EntityID("1234")); magicUnit.setName("Unit"); - magicUnit.setId(new EntityID(24)); + magicUnit.setId(new EntityID("24")); magicUnit.setDatatype("TEXT"); - final Entity newEntity = new RetrieveEntity(new EntityID(1234)); - final Property newProperty = new Property(new RetrieveEntity(new EntityID(1))); + final Entity newEntity = new RetrieveEntity(new EntityID("1234")); + final Property newProperty = new Property(new RetrieveEntity(new EntityID("1"))); - final Property newUnit = new Property(new RetrieveEntity(new EntityID(5))); + final Property newUnit = new Property(new RetrieveEntity(new EntityID("5"))); newUnit.setName(magicUnit.getName()); newUnit.setId(magicUnit.getId()); newUnit.setDatatype(magicUnit.getDatatype()); @@ -126,10 +127,10 @@ public class UpdateTest { newEntity.addProperty(newProperty); - final Entity oldEntity = new RetrieveEntity(new EntityID(1234)); - final Property oldProperty = new Property(new RetrieveEntity(new EntityID(1))); + final Entity oldEntity = new RetrieveEntity(new EntityID("1234")); + final Property oldProperty = new Property(new RetrieveEntity(new EntityID("1"))); - final Property oldUnit = new Property(new RetrieveEntity(new EntityID(5))); + final Property oldUnit = new Property(new RetrieveEntity(new EntityID("5"))); oldUnit.setName(magicUnit.getName()); oldUnit.setId(magicUnit.getId()); oldUnit.setDatatype(magicUnit.getDatatype()); @@ -149,15 +150,15 @@ public class UpdateTest { @Test public void testDeriveUpdate_DifferentUnit() throws NoSuchAlgorithmException, IOException, CaosDBException { - final EntityInterface magicUnit = new RetrieveEntity(new EntityID(1234)); + final EntityInterface magicUnit = new RetrieveEntity(new EntityID("1234")); magicUnit.setName("Unit"); - magicUnit.setId(new EntityID(24)); + magicUnit.setId(new EntityID("24")); magicUnit.setDatatype("TEXT"); - final Entity newEntity = new RetrieveEntity(new EntityID(1234)); - final Property newProperty = new Property(new RetrieveEntity(new EntityID(1))); + final Entity newEntity = new RetrieveEntity(new EntityID("1234")); + final Property newProperty = new Property(new RetrieveEntity(new EntityID("1"))); - final Property newUnit = new Property(new RetrieveEntity(new EntityID(5))); + final Property newUnit = new Property(new RetrieveEntity(new EntityID("5"))); newUnit.setName(magicUnit.getName()); newUnit.setId(magicUnit.getId()); newUnit.setDatatype(magicUnit.getDatatype()); @@ -168,10 +169,10 @@ public class UpdateTest { newEntity.addProperty(newProperty); - final Entity oldEntity = new RetrieveEntity(new EntityID(1234)); - final Property oldProperty = new Property(new RetrieveEntity(new EntityID(1))); + final Entity oldEntity = new RetrieveEntity(new EntityID("1234")); + final Property oldProperty = new Property(new RetrieveEntity(new EntityID("1"))); - final Property oldUnit = new Property(new RetrieveEntity(new EntityID(5))); + final Property oldUnit = new Property(new RetrieveEntity(new EntityID("5"))); oldUnit.setName(magicUnit.getName()); oldUnit.setId(magicUnit.getId()); oldUnit.setDatatype(magicUnit.getDatatype()); @@ -192,10 +193,10 @@ public class UpdateTest { throws NoSuchAlgorithmException, CaosDBException, IOException { final Entity newEntity = new UpdateEntity(); - final Property newProperty = new Property(new UpdateEntity(new EntityID(1))); + final Property newProperty = new Property(new UpdateEntity(new EntityID("1"))); newProperty.setDatatype("List<Person>"); CollectionValue newValue = new CollectionValue(); - newValue.add(new ReferenceValue(1234)); + newValue.add(new ReferenceValue("1234")); newValue.add(null); newValue.add(new GenericValue(2345)); newValue.add(new GenericValue(3465)); @@ -206,14 +207,14 @@ public class UpdateTest { // old entity represents the stored entity. final Entity oldEntity = new UpdateEntity(); - final Property oldProperty = new Property(new UpdateEntity(new EntityID(1))); + final Property oldProperty = new Property(new UpdateEntity(new EntityID("1"))); oldProperty.setDatatype("List<Person>"); CollectionValue oldValue = new CollectionValue(); // Values are shuffled but have the correct index oldValue.add(1, null); oldValue.add(3, new GenericValue(3465)); - oldValue.add(2, new ReferenceValue(2345)); - oldValue.add(0, new ReferenceValue(1234)); + oldValue.add(2, new ReferenceValue("2345")); + oldValue.add(0, new ReferenceValue("1234")); oldProperty.setValue(oldValue); oldEntity.addProperty(oldProperty); @@ -245,8 +246,8 @@ public class UpdateTest { // Values are shuffled but have the correct index oldValue2.add(0, null); oldValue2.add(1, new GenericValue(3465)); - oldValue2.add(2, new ReferenceValue(2345)); - oldValue2.add(3, new ReferenceValue(1234)); + oldValue2.add(2, new ReferenceValue("2345")); + oldValue2.add(3, new ReferenceValue("1234")); oldValue2.add(4, null); oldProperty.setValue(oldValue2); @@ -266,8 +267,8 @@ public class UpdateTest { public void testDeriveUpdate_UpdateList() throws NoSuchAlgorithmException, CaosDBException, IOException { // @review Florian Spreckelsen 2022-03-15 - final Property oldProperty = new Property(new UpdateEntity(new EntityID(1))); - final Property newProperty = new Property(new UpdateEntity(new EntityID(1))); + final Property oldProperty = new Property(new UpdateEntity(new EntityID("1"))); + final Property newProperty = new Property(new UpdateEntity(new EntityID("1"))); oldProperty.setDatatype("List<Person>"); newProperty.setDatatype("List<Person>"); final Entity oldEntity = new UpdateEntity();