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 2c1f5481af947c60e4f3192a7935183537ff3a05..be47de54cd33c917b8997eebaa55c31edfe71543 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,22 @@ 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). +## [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 ### + +* Wrong url returned by FileSystem resource behind proxy. +* `NullPointerException` in GRPC API converters when executing SELECT query on + NULL values. + ## [0.10.0] - 2023-06-02 ## (Florian Spreckelsen) diff --git a/CITATION.cff b/CITATION.cff index a45e422c288822d0c34e1f5df2bcb83ceedd8e23..962c0cfce8443708de4094ea83658bd468ada26d 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.10.0 +version: 0.11.0 doi: 10.3390/data4020083 -date-released: 2023-06-02 +date-released: 2023-10-13 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 c030d6eb63d61ad2d9adc6a174877b1006396537..5df6c40ca04a9c6f22d03f61c937ab5fbeab565f 100644 --- a/conf/core/server.conf +++ b/conf/core/server.conf @@ -100,6 +100,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 # -------------------------------------------------- @@ -229,4 +244,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/pom.xml b/pom.xml index 835de2d3eebef06cb10e9efd2e4aaf6890287357..05d1aa4b7d8e01b5cd3794b6fabbd01ea81a24ac 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.10.0</version> + <version>0.11.0</version> <packaging>jar</packaging> <name>CaosDB Server</name> <scm> diff --git a/src/doc/conf.py b/src/doc/conf.py index 3c062b94cff76b69ac234ab7512a02c0b6d27247..434ed4623fbacd240d35142f855c1fd4301de915 100644 --- a/src/doc/conf.py +++ b/src/doc/conf.py @@ -26,9 +26,9 @@ copyright = '2023, IndiScale GmbH' author = 'Daniel Hornung, Timm Fitschen' # The short X.Y version -version = '0.10.0' +version = '0.11.0' # The full version, including alpha/beta/rc tags -release = '0.10.0' +release = '0.11.0' # -- General configuration --------------------------------------------------- diff --git a/src/main/java/org/caosdb/server/CaosDBServer.java b/src/main/java/org/caosdb/server/CaosDBServer.java index 9daabaed65aa39a9be868567581588625aba7a53..2a115c5cfa06ed0e3db85847c27fb4c8424eadaf 100644 --- a/src/main/java/org/caosdb/server/CaosDBServer.java +++ b/src/main/java/org/caosdb/server/CaosDBServer.java @@ -108,6 +108,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; @@ -223,7 +224,6 @@ public class CaosDBServer extends Application { public boolean notifyObserver(String e, Observable sender) { if (e.equals(ServerProperties.KEY_TIMEZONE)) { - String[] availableIDs = TimeZone.getAvailableIDs(); TimeZone newZoneId = TimeZone.getTimeZone(getServerProperty(ServerProperties.KEY_TIMEZONE)); TimeZone.setDefault(newZoneId); @@ -709,6 +709,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 @@ -894,6 +897,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 d58250242f691498288d765dd252986eda7fb09f..93a0c7473be1c31565e4b159ba55e01d557687e7 100644 --- a/src/main/java/org/caosdb/server/ServerProperties.java +++ b/src/main/java/org/caosdb/server/ServerProperties.java @@ -150,6 +150,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/ACMPermissions.java b/src/main/java/org/caosdb/server/accessControl/ACMPermissions.java index 854fc07f9ed93eda28e8aca99c1ea1983c73f4fe..075036921aba51acc05ff1b76df01763485c53c2 100644 --- a/src/main/java/org/caosdb/server/accessControl/ACMPermissions.java +++ b/src/main/java/org/caosdb/server/accessControl/ACMPermissions.java @@ -26,12 +26,9 @@ import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; public class ACMPermissions implements Comparable<ACMPermissions> { - private static Logger LOGGER = LoggerFactory.getLogger(ACMPermissions.class); public static final String USER_PARAMETER = "?USER?"; public static final String REALM_PARAMETER = "?REALM?"; public static final String ROLE_PARAMETER = "?ROLE?"; diff --git a/src/main/java/org/caosdb/server/database/BackendTransaction.java b/src/main/java/org/caosdb/server/database/BackendTransaction.java index 5addf3f3f3e34a8e3e6a9152b9511046fe4b6a23..05ea26c255ae5b2bd1e75df7f3fdc4d94fca5e5a 100644 --- a/src/main/java/org/caosdb/server/database/BackendTransaction.java +++ b/src/main/java/org/caosdb/server/database/BackendTransaction.java @@ -47,7 +47,6 @@ import org.caosdb.server.database.backend.implementation.MySQL.MySQLIsSubType; 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.MySQLRetrieveAllUncheckedFiles; import org.caosdb.server.database.backend.implementation.MySQL.MySQLRetrieveDatatypes; @@ -106,7 +105,6 @@ import org.caosdb.server.database.backend.interfaces.IsSubTypeImpl; 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.RetrieveAllUncheckedFilesImpl; import org.caosdb.server.database.backend.interfaces.RetrieveDatatypesImpl; @@ -177,7 +175,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); 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 5280de17d91982db0481105af8c9ca760db1384f..0000000000000000000000000000000000000000 --- a/src/main/java/org/caosdb/server/database/DatabaseUtils.java +++ /dev/null @@ -1,380 +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; - -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); - p.setDatatype(e.getDatatype()); - p.setValue(e.getValue()); - 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)) { - // this property can be represented without any replacement. We explicitly - // setReplacement(null) because there is a corner case (related to the inheritance of - // properties) where there is a replacement present which belongs to the parent entity, see - // https://gitlab.com/caosdb/caosdb-server/-/issues/216. - p.setReplacement(null); - } 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.filePath = bytes2UTF8(rs.getBytes("FilePath")); - ret.fileSize = rs.getLong("FileSize"); - ret.fileHash = bytes2UTF8(rs.getBytes("FileHash")); - - ret.versionId = bytes2UTF8(rs.getBytes("Version")); - return ret; - } - - 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/backend/implementation/MySQL/DatabaseUtils.java b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/DatabaseUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..f7d364ae6376524ef892de3353d699547417717e --- /dev/null +++ b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/DatabaseUtils.java @@ -0,0 +1,365 @@ +/* + * 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; + +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); + } else { + Replacement r = new Replacement(p); + stage1Inserts.add(r); + stage1Inserts.add(r.replacement); + } + processSubPropertiesStage1(stage1Inserts, p); + } + + 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) { + + EntityID replacementId = replacementIds.pop(); + ((Replacement) p).setReplacementId(replacementId); + + if (p.hasProperties()) { + for (Property subP : p.getProperties()) { + subP.setDomain(p); + stage2Inserts.add(subP); + } + } + } else { + if (p.hasProperties()) { + for (Property subP : p.getProperties()) { + 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()) { + if (subP.hasProperties()) { + stage1Inserts.add(new Replacement(subP)); + processSubPropertiesStage1(stage1Inserts, subP); + } + } + } + + public static void parseOverrides(final List<ProtoProperty> 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<ProtoProperty> parsePropertyResultset(final ResultSet rs) throws SQLException { + final List<ProtoProperty> ret = new LinkedList<>(); + while (rs.next()) { + ProtoProperty pp = new ProtoProperty(); + pp.id = rs.getInt("PropertyID"); + 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.getInt("EntityID"); + ret.description = bytes2UTF8(rs.getBytes("EntityDesc")); + ret.datatype = bytes2UTF8(rs.getBytes("Datatype")); + ret.collection = bytes2UTF8(rs.getBytes("Collection")); + + ret.filePath = bytes2UTF8(rs.getBytes("FilePath")); + ret.fileSize = rs.getLong("FileSize"); + ret.fileHash = bytes2UTF8(rs.getBytes("FileHash")); + + ret.versionId = bytes2UTF8(rs.getBytes("Version")); + return ret; + } + + 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.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 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().toInteger())) { + 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); + } + } + 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.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 = replacement.type; + 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/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/MySQLInsertEntityProperties.java b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLInsertEntityProperties.java index 7c3c5a33e160f3da6a8c2b89fe0487e0317c99da..becb56a2397fffa323f005507463a586bdecc0cf 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 @@ -25,15 +25,28 @@ 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; public class MySQLInsertEntityProperties extends MySQLTransaction implements InsertEntityPropertiesImpl { @@ -46,7 +59,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, @@ -76,10 +109,124 @@ public class MySQLInsertEntityProperties extends MySQLTransaction 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().toInteger(); + 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().toInteger(); + 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 = + ((AbstractCollectionDatatype) property.getDatatype()) + .getDatatype() + .getId() + .toString(); + fp.collection = + ((AbstractCollectionDatatype) property.getDatatype()).getCollectionName(); + } else { + fp.type = property.getDatatype().getId().toString(); + } + } + } + + insertFlatProperty( + domain, entity != null ? entity : property.getDomain(), fp, table, unit_sig); + } + } + + public static final String STMT_REGISTER_SUBDOMAIN = "call registerSubdomain(?)"; + + 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.getInt(1))); + } + return ret; + } + } } 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 1b945acf566db938f12847f758636dbfddeac3d1..27227926156fc9b35ab75473e1efa2cce55f152e 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 @@ -26,7 +26,6 @@ import java.sql.PreparedStatement; import java.sql.ResultSet; 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.InsertSparseEntityImpl; import org.caosdb.server.database.exceptions.IntegrityException; 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..9a2ea81629853e3baf9de7757e1abe6c8f01fe28 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; 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/MySQLRetrieveParents.java b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveParents.java index 64ce7e9023bd18506a4b13610033923fa7320ca8..1e2a6621d2fb85a5a5260b1a607670e970f54701 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,7 +42,7 @@ 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; 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..100df4ce72ebae75d3022866b345bacc9752ad91 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 @@ -26,13 +26,11 @@ 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; @@ -46,27 +44,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 = + final List<ProtoProperty> propsStage1 = 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; + for (final ProtoProperty p : propsStage1) { - final List<FlatProperty> subProps = + p.subProperties = retrieveFlatPropertiesStage1(entity.toInteger(), p.id, version, prepareStatement); - - proto.subProperties = subProps; - - protos.add(proto); } - return protos; + return DatabaseUtils.transformToDeepPropertyTree(propsStage1, isHead); } catch (final SQLException e) { throw new TransactionException(e); } catch (final ConnectionException e) { @@ -74,43 +65,38 @@ public class MySQLRetrieveProperties extends MySQLTransaction implements Retriev } } - private List<FlatProperty> retrieveFlatPropertiesStage1( + private List<ProtoProperty> retrieveFlatPropertiesStage1( final Integer domain, final Integer 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); - } + 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.setInt(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 List<ProtoProperty> properties = DatabaseUtils.parsePropertyResultset(rs); - final PreparedStatement stmt2 = prepareStatement(stmtStr2); + final PreparedStatement retrieveOverrides = prepareStatement(stmtStr2); - retrieveOverrides(domain, entity, version, stmt2, props); + retrieveOverrides(domain, entity, version, retrieveOverrides, properties); - return props; - } finally { - if (rs != null && !rs.isClosed()) { - rs.close(); - } + return properties; } } @@ -118,24 +104,25 @@ public class MySQLRetrieveProperties extends MySQLTransaction implements Retriev final Integer domain, final Integer 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.setInt(1, domain); + retrieveOverrides.setInt(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/MySQLRetrieveSparseEntity.java b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveSparseEntity.java index ca3bd078daba4c72e7f516ce64b1da1515a1a6eb..48cd84b3869262ba7f1d8129fdde2335bed6cd8e 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; 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..40f036f1d63753c0be9e6153f741db7d4f9d04ad 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; 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..6c4c2629a5effee25128f478bef20f5558a027ba 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; 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 1c8039253b4c5e74a8054dfa937fec23c55440e8..b2d0fb9e3b7615e7e95243a2a7bde2fa99e98ca2 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; 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..61e431bab60b9973f6aa1f0b824ffc3e3b60744e --- /dev/null +++ b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/Replacement.java @@ -0,0 +1,63 @@ +/* + * 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; + + @Override + public EntityID getId() { + return replacement.getId(); + } + + 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(0); + } +} 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/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/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 5871c97e8ff172fe58b37627cc93824e96c7b32e..2846fa64d56ea696d4ecb0412a88de449d0483a1 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 @@ -54,7 +54,9 @@ public class InsertEntityTransaction extends BackendTransaction { if (newEntity.hasFileProperties()) { execute(new InsertFile(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/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/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/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/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/entity/Entity.java b/src/main/java/org/caosdb/server/entity/Entity.java index 48367508112c2c3267abd771fa84eb21ea16a534..614397c50526606176fc50b06e2f07e76cbfb9ff 100644 --- a/src/main/java/org/caosdb/server/entity/Entity.java +++ b/src/main/java/org/caosdb/server/entity/Entity.java @@ -47,7 +47,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; @@ -422,7 +421,7 @@ public abstract class Entity extends AbstractObservable implements EntityInterfa } } - private StatementStatus statementStatus = null; + private StatementStatusInterface statementStatus = null; /** * statementStatus getter. @@ -430,7 +429,7 @@ public abstract class Entity extends AbstractObservable implements EntityInterfa * @return */ @Override - public StatementStatus getStatementStatus() { + public StatementStatusInterface getStatementStatus() { return this.statementStatus; } @@ -440,7 +439,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; } @@ -609,10 +608,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 @@ -848,23 +848,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 diff --git a/src/main/java/org/caosdb/server/entity/EntityInterface.java b/src/main/java/org/caosdb/server/entity/EntityInterface.java index f967b307a1ea13a16d7e9225cb038a15f6883727..ba77b5c4ac8b5c833d2046dec506655531ff62cb 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; @@ -58,9 +57,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); @@ -127,12 +126,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(); 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/RegisterSubDomainImpl.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/RegisterSubDomainImpl.java rename to src/main/java/org/caosdb/server/entity/StatementStatusInterface.java index 87ea0c85db6a698f49c103506b520342deff2fb0..d1596afda95a4836dcd84fc79b67cf2e0b4f12a4 100644 --- a/src/main/java/org/caosdb/server/database/backend/interfaces/RegisterSubDomainImpl.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.Deque; -import org.caosdb.server.database.exceptions.TransactionException; -import org.caosdb.server.entity.EntityID; +package org.caosdb.server.entity; -public interface RegisterSubDomainImpl extends BackendTransactionImpl { - - public abstract Deque<EntityID> execute(int domainCount) 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/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 7da945738ac966114a020098171c049a6fa387a3..b294a17e5ea68ee02e529735ec39a748bc4f2c83 100644 --- a/src/main/java/org/caosdb/server/entity/wrapper/EntityWrapper.java +++ b/src/main/java/org/caosdb/server/entity/wrapper/EntityWrapper.java @@ -38,7 +38,7 @@ import org.caosdb.server.entity.EntityInterface; import org.caosdb.server.entity.FileProperties; 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..9268d7f3cb327a70a4cca872657d72e2779fc344 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,13 @@ 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.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 +74,45 @@ 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 != null) { + if (pp.collection != null) { this.setDatatype( - AbstractCollectionDatatype.collectionDatatypeFactory(fp.collection, fp.type)); + AbstractCollectionDatatype.collectionDatatypeFactory(pp.collection, pp.type)); } else { - this.setDatatype(fp.type); + this.setDatatype(pp.type); } 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..35d94aa79fd62a04f8b0dae1a17829a1106c2d97 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. * 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/grpc/CaosDBToGrpcConverters.java b/src/main/java/org/caosdb/server/grpc/CaosDBToGrpcConverters.java index 6fef590746bbf2c3ff802baec73385e4b061ec95..8ab8b0579bd6d0a32976ac6e2e28e776f00d4850 100644 --- a/src/main/java/org/caosdb/server/grpc/CaosDBToGrpcConverters.java +++ b/src/main/java/org/caosdb/server/grpc/CaosDBToGrpcConverters.java @@ -75,6 +75,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; @@ -298,6 +299,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; } @@ -357,8 +360,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: @@ -704,7 +707,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); @@ -735,8 +738,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/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/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/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/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 0de160cdb6249ffaea10f4eeeafdf4884323a186..ebc35a16b1495accf8cd4a114dd688c2b2529c1b 100644 --- a/src/main/java/org/caosdb/server/query/POV.java +++ b/src/main/java/org/caosdb/server/query/POV.java @@ -25,7 +25,7 @@ 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; diff --git a/src/main/java/org/caosdb/server/query/Query.java b/src/main/java/org/caosdb/server/query/Query.java index c4a458630456d057ccddf26c48def0e72ccf3789..abaca4365e54c21ee2ba8cf8bf7727fe746c7277 100644 --- a/src/main/java/org/caosdb/server/query/Query.java +++ b/src/main/java/org/caosdb/server/query/Query.java @@ -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; 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/FileSystemResource.java b/src/main/java/org/caosdb/server/resource/FileSystemResource.java index 1e601cb2116c92ae3aa31aaca4be01b429f9e826..9c0d615833148f706b190d7dfbc1aafdca61a75f 100644 --- a/src/main/java/org/caosdb/server/resource/FileSystemResource.java +++ b/src/main/java/org/caosdb/server/resource/FileSystemResource.java @@ -86,14 +86,16 @@ public class FileSystemResource extends AbstractCaosDBServerResource { } if (file.isDirectory()) { - String referenceString = getReference().toString(); - if (!referenceString.endsWith("/")) { - referenceString = referenceString + "/"; - } + String path = (specifier.endsWith("/") ? specifier : specifier + "/"); + String url = + getUtils().getServerRootURI() + + "/FileSystem" + + (path.startsWith("/") ? path : ("/" + path)); + final Element folder = new Element("dir"); - folder.setAttribute("path", (specifier.endsWith("/") ? specifier : specifier + "/")); + folder.setAttribute("path", path); folder.setAttribute("name", file.getName()); - folder.setAttribute("url", referenceString); + folder.setAttribute("url", url); final boolean thumbnailsExist = new File(file.getAbsolutePath() + File.separator + ".thumbnails").exists(); @@ -110,7 +112,7 @@ public class FileSystemResource extends AbstractCaosDBServerResource { } if (thumbnailsExist) { - final Attribute thumbnailAttribute = getThumbnailAttribute(file, child, referenceString); + final Attribute thumbnailAttribute = getThumbnailAttribute(file, child, url); if (thumbnailAttribute != null) { celem.setAttribute(thumbnailAttribute); } diff --git a/src/main/java/org/caosdb/server/utils/ResultSetIterator.java b/src/main/java/org/caosdb/server/utils/ResultSetIterator.java index 0912d278c7158c9b7b75a585c2827d851951f29b..71429f1a704299555ab1d86be0695e3104be828d 100644 --- a/src/main/java/org/caosdb/server/utils/ResultSetIterator.java +++ b/src/main/java/org/caosdb/server/utils/ResultSetIterator.java @@ -1,6 +1,6 @@ package org.caosdb.server.utils; -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; 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/java/org/caosdb/server/database/InsertTest.java b/src/test/java/org/caosdb/server/database/backend/implementation/MySQL/InsertTest.java similarity index 55% rename from src/test/java/org/caosdb/server/database/InsertTest.java rename to src/test/java/org/caosdb/server/database/backend/implementation/MySQL/InsertTest.java index 524e7addd20581db349082f91206605a97bdee8e..03bd185c8884822f89f8ff323a1c32125b66f88b 100644 --- a/src/test/java/org/caosdb/server/database/InsertTest.java +++ b/src/test/java/org/caosdb/server/database/backend/implementation/MySQL/InsertTest.java @@ -20,21 +20,24 @@ * * ** end header */ -package org.caosdb.server.database; +package org.caosdb.server.database.backend.implementation.MySQL; -import static org.caosdb.server.database.DatabaseUtils.deriveStage1Inserts; -import static org.caosdb.server.database.DatabaseUtils.deriveStage2Inserts; +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.EntityInterface; import org.caosdb.server.entity.InsertEntity; import org.caosdb.server.entity.RetrieveEntity; import org.caosdb.server.entity.Role; @@ -44,6 +47,14 @@ 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(-i)); + } + return replacementIds; + } + /** * Record with very deep property tree. * @@ -122,79 +133,67 @@ public class InsertTest { 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); + 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()); - assertEquals(Role.Property, stage1Inserts.get(0).getRole()); - assertEquals(new EntityID(1), stage1Inserts.get(0).getId()); + assertFalse(stage1Inserts.get(0) instanceof Replacement); + assertEquals(1, stage1Inserts.get(0).getId().toInteger()); 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()); + assertFalse(stage1Inserts.get(1) instanceof Replacement); + assertEquals(2, stage1Inserts.get(1).getId().toInteger()); 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()); + assertFalse(stage1Inserts.get(2) instanceof Replacement); + assertEquals(4, stage1Inserts.get(2).getId().toInteger()); assertEquals("V4", ((SingleValue) stage1Inserts.get(2).getValue()).toDatabaseString()); - assertFalse(stage1Inserts.get(2).hasReplacement()); - assertEquals(Role.Domain, stage1Inserts.get(3).getRole()); + assertTrue(stage1Inserts.get(3) instanceof Replacement); + assertEquals(-1, stage1Inserts.get(3).getId().toInteger()); 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()); + assertFalse(stage1Inserts.get(4) instanceof Replacement); + assertEquals(7, stage1Inserts.get(4).getId().toInteger()); assertEquals("V7", ((SingleValue) stage1Inserts.get(4).getValue()).toDatabaseString()); - assertFalse(stage1Inserts.get(4).hasReplacement()); - assertEquals(Role.Domain, stage1Inserts.get(5).getRole()); + assertTrue(stage1Inserts.get(5) instanceof Replacement); + assertEquals(-2, stage1Inserts.get(5).getId().toInteger()); assertEquals("V8", ((SingleValue) stage1Inserts.get(5).getValue()).toDatabaseString()); - assertFalse(stage1Inserts.get(5).hasReplacement()); - assertEquals(Role.Domain, stage1Inserts.get(6).getRole()); + assertTrue(stage1Inserts.get(6) instanceof Replacement); + assertEquals(-3, stage1Inserts.get(6).getId().toInteger()); 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(new EntityID(2), stage2Inserts.get(0).getDomain()); - 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(new EntityID(4), stage2Inserts.get(1).getDomain()); - 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(new EntityID(-1), stage2Inserts.get(2).getDomain()); - 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(new EntityID(7), stage2Inserts.get(3).getDomain()); - 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(new EntityID(-2), stage2Inserts.get(4).getDomain()); - 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()); + assertEquals(new EntityID(-3), stage2Inserts.get(5).getDomain()); } /** @@ -227,34 +226,31 @@ public class InsertTest { subp.setStatementStatus(StatementStatus.FIX); p2.addProperty(subp); - final LinkedList<EntityInterface> stage1Inserts = new LinkedList<EntityInterface>(); - final LinkedList<EntityInterface> stage2Inserts = new LinkedList<EntityInterface>(); + final LinkedList<Property> stage1Inserts = new LinkedList<>(); + final LinkedList<Property> stage2Inserts = new LinkedList<>(); - deriveStage1Inserts(stage1Inserts, r); + int replacementCount = deriveStage1Inserts(stage1Inserts, r); + deriveStage2Inserts(stage2Inserts, stage1Inserts, registerReplacementIds(replacementCount), r); assertEquals(3, stage1Inserts.size()); - assertEquals(Role.Property, stage1Inserts.get(0).getRole()); + + assertFalse(stage1Inserts.get(0) instanceof Replacement); 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()); + assertTrue(stage1Inserts.get(1) instanceof Replacement); 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(new EntityID(-1), stage1Inserts.get(1).getId()); - assertEquals(Role.Domain, stage1Inserts.get(2).getRole()); - assertEquals("V1-2", ((SingleValue) stage1Inserts.get(2).getValue()).toDatabaseString()); - assertFalse(stage1Inserts.get(2).hasReplacement()); - - deriveStage2Inserts(stage2Inserts, stage1Inserts); + 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()); - assertEquals(Role.Property, stage2Inserts.get(0).getRole()); + + assertFalse(stage2Inserts.get(0) instanceof Replacement); assertEquals(new EntityID(2), stage2Inserts.get(0).getId()); assertEquals("V2", ((SingleValue) stage2Inserts.get(0).getValue()).toDatabaseString()); - assertFalse(stage2Inserts.get(0).hasReplacement()); } /** @@ -291,39 +287,33 @@ public class InsertTest { sub1.setStatementStatus(StatementStatus.FIX); p1.addProperty(sub1); - final LinkedList<EntityInterface> stage1Inserts = new LinkedList<EntityInterface>(); - final LinkedList<EntityInterface> stage2Inserts = new LinkedList<EntityInterface>(); + final LinkedList<Property> stage1Inserts = new LinkedList<>(); + final LinkedList<Property> stage2Inserts = new LinkedList<>(); - deriveStage1Inserts(stage1Inserts, r); + int count = deriveStage1Inserts(stage1Inserts, r); + deriveStage2Inserts(stage2Inserts, stage1Inserts, registerReplacementIds(count), 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()); + assertTrue(stage1Inserts.get(0) instanceof Replacement); + assertEquals(new EntityID(-1), stage1Inserts.get(0).getId()); assertEquals(null, stage1Inserts.get(0).getValue()); - assertEquals(Role.Domain, stage1Inserts.get(1).getRole()); - assertFalse(stage1Inserts.get(1).hasReplacement()); - assertEquals(null, stage1Inserts.get(1).getValue()); + assertFalse(stage1Inserts.get(1) instanceof Replacement); + assertEquals(new EntityID(-1), stage1Inserts.get(1).getId()); + assertEquals(1, ((ReferenceValue) stage1Inserts.get(1).getValue()).getId().toInteger()); - assertEquals(Role.Property, stage1Inserts.get(2).getRole()); + assertFalse(stage1Inserts.get(2) instanceof Replacement); 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()); + assertFalse(stage1Inserts.get(3) instanceof Replacement); 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()); + assertEquals(new EntityID(-1), stage2Inserts.get(0).getDomain()); } /** @@ -360,32 +350,154 @@ public class InsertTest { p3.setStatementStatus(StatementStatus.FIX); r.addProperty(p3); - final LinkedList<EntityInterface> stage1Inserts = new LinkedList<EntityInterface>(); - final LinkedList<EntityInterface> stage2Inserts = new LinkedList<EntityInterface>(); + final LinkedList<Property> stage1Inserts = new LinkedList<>(); + final LinkedList<Property> stage2Inserts = new LinkedList<>(); - deriveStage1Inserts(stage1Inserts, r); + int count = deriveStage1Inserts(stage1Inserts, r); + deriveStage2Inserts(stage2Inserts, stage1Inserts, registerReplacementIds(count), r); assertEquals(4, stage1Inserts.size()); - assertEquals(Role.Property, stage1Inserts.get(0).getRole()); + + assertFalse(stage1Inserts.get(0) instanceof Replacement); 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) instanceof Replacement); + assertEquals(new EntityID(-1), stage1Inserts.get(1).getId()); 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); + assertFalse(stage1Inserts.get(2) instanceof Replacement); + assertEquals(new EntityID(-1), stage1Inserts.get(2).getId()); + assertEquals(2, ((ReferenceValue) stage1Inserts.get(2).getValue()).getId().toInteger()); - assertEquals(Role.Property, stage1Inserts.get(3).getRole()); + assertFalse(stage1Inserts.get(3) instanceof Replacement); assertEquals(new EntityID(3), stage1Inserts.get(3).getId()); - assertFalse(stage1Inserts.get(3).hasReplacement()); assertEquals("V3", ((SingleValue) stage1Inserts.get(3).getValue()).toDatabaseString()); - deriveStage2Inserts(stage2Inserts, stage1Inserts); + 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/grpc/CaosDBToGrpcConvertersTest.java b/src/test/java/org/caosdb/server/grpc/CaosDBToGrpcConvertersTest.java index d39a803f9cfe3ce0a4cf23265683d121b3bc688d..d46c187324b76e179ef8d562cd5a150ab7968a45 100644 --- a/src/test/java/org/caosdb/server/grpc/CaosDBToGrpcConvertersTest.java +++ b/src/test/java/org/caosdb/server/grpc/CaosDBToGrpcConvertersTest.java @@ -1,12 +1,14 @@ 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.FileProperties; @@ -17,6 +19,7 @@ import org.caosdb.server.entity.StatementStatus; import org.caosdb.server.entity.wrapper.Parent; import org.caosdb.server.entity.wrapper.Property; import org.caosdb.server.entity.xml.IdAndServerMessagesOnlyStrategy; +import org.caosdb.server.query.Query.Selection; import org.junit.jupiter.api.Test; public class CaosDBToGrpcConvertersTest { @@ -27,7 +30,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"); @@ -101,4 +106,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()); + } }