diff --git a/.gitlab/merge_request_templates/Default.md b/.gitlab/merge_request_templates/Default.md index c1c223101a73ba39c46288fb75aab2a73a16ca6d..0d4d8e2391da91f15f09f37e91f1cf931b972e24 100644 --- a/.gitlab/merge_request_templates/Default.md +++ b/.gitlab/merge_request_templates/Default.md @@ -28,6 +28,7 @@ guidelines](https://gitlab.com/linkahead/linkahead/-/blob/dev/REVIEW_GUIDELINES. - [ ] 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/linkahead/linkahead/-/blob/dev/REVIEW_GUIDELINES. - [ ] 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 2893d57d5bad09520c4b6641ea536fe8de1cd654..943b0ef2a43d71e9dd56003e543fb1522e424059 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,31 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### 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/". + +### Changed ### + +### Deprecated ### + +### Removed ### + +### Fixed ### + +* Wrong url returned by FileSystem resource behind proxy. +* `NullPointerException` in GRPC API converters when executing SELECT query on + NULL values. + +### Security ### + +### Documentation ### + +## [0.10.0] - 2023-06-02 ## +(Florian Spreckelsen) + ### Changed ### * Renamed CaosDB to LinkAhead. @@ -27,13 +52,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * The InsertFilesInDir FlagJob now creates File entities without a name. The previous behavior caused severe performance problems for very large numbers of files. Issue: [#197](https://gitlab.com/caosdb/caosdb-server/-/issues/197) -### Deprecated ### - -### Removed ### - ### Fixed ### -- [#203](https://gitlab.com/caosdb/caosdb-server/-/issues/203) +* Unexpected Server Error when inserting an Entity. + [#216](https://gitlab.com/caosdb/caosdb-server/-/issues/216) +* Bad performance due to the execution of unnecessary jobs during retrieval. + [#189](https://gitlab.com/caosdb/caosdb-server/-/issues/189) +* Query Language: Parentheses change filter to subproperty filter + [#203](https://gitlab.com/caosdb/caosdb-server/-/issues/203) * Searching for values in scientific notation [#143](https://gitlab.com/caosdb/caosdb-server/-/issues/143) * Denying a role permission has no effect diff --git a/CITATION.cff b/CITATION.cff index 55eacbbdd6e2564355e4081ff72f6e82352d8f3c..1fdb5ba825be628e3fe25458918e99b65f974d19 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -23,6 +23,6 @@ authors: given-names: Stefan orcid: https://orcid.org/0000-0001-7214-8125 title: "LinkAhead - Server" -version: 0.8.1 +version: 0.10.0 doi: 10.3390/data4020083 -date-released: 2022-11-07 +date-released: 2023-06-02 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 99a66259ba354c40cdc096a4b5022dd0b873cbd4..ef5df36c0805866ea4945f10042772e05d61a241 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 4296c2a5d3f44e59c3313a6234ab7db38dd7e6c6..3cc7ee6e5b2c7a4eb965ba7572fec9e173845567 100644 --- a/pom.xml +++ b/pom.xml @@ -27,7 +27,7 @@ <modelVersion>4.0.0</modelVersion> <groupId>org.linkahead</groupId> <artifactId>linkahead-server</artifactId> - <version>0.9.1-SNAPSHOT</version> + <version>0.10.1-SNAPSHOT</version> <packaging>jar</packaging> <name>LinkAhead Server</name> <scm> diff --git a/src/doc/conf.py b/src/doc/conf.py index b87d4693f1e6bd47e7f5becd8e467bfd6d2d92e5..cd2020806caaae16e90f1a31e93da0cc513b60b0 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.9.1' +version = '0.10.1' # The full version, including alpha/beta/rc tags -release = '0.9.1-SNAPSHOT' +release = '0.10.1-SNAPSHOT' # -- General configuration --------------------------------------------------- diff --git a/src/main/java/org/caosdb/server/CaosDBServer.java b/src/main/java/org/caosdb/server/CaosDBServer.java index 59de3ce095596e4bf99a5d5f087b020d7b0c3ad0..c22581e0fece277244fca013ed5a30077a1c565d 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 LinkAheadServer 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 LinkAheadServer 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 LinkAheadComponent extends Component { public LinkAheadComponent() { super(); + String responseLogFormat = + LinkAheadServer.getServerProperty(ServerProperties.KEY_REST_RESPONSE_LOG_FORMAT); + if ("OFF".equalsIgnoreCase(responseLogFormat)) { + getLogService().setEnabled(false); + } else if (responseLogFormat != null && !responseLogFormat.isEmpty()) { + getLogService().setResponseLogFormat(responseLogFormat); + } setName(LinkAheadServer.getServerProperty(ServerProperties.KEY_SERVER_NAME)); setOwner(LinkAheadServer.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 5f9a56762737147c2a966f8abf8e12c1b32be3fe..9e60eb6314f66e5143a0901f3564be3bfdbf64eb 100644 --- a/src/main/java/org/caosdb/server/ServerProperties.java +++ b/src/main/java/org/caosdb/server/ServerProperties.java @@ -152,6 +152,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 4e8748a329f554052437ca9afff6ce3f206a5ffe..5495f9336ca93252b4442351dbab51d4a06f9bad 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 117a2585cb84e65cb0c84ac15ec5424a3d873447..16b0912f0d58920588d4f5c81c6c2347478deb2d 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.linkahead.server.database.backend.implementation.MySQL.MySQLIsSubType import org.linkahead.server.database.backend.implementation.MySQL.MySQLListRoles; import org.linkahead.server.database.backend.implementation.MySQL.MySQLListUsers; import org.linkahead.server.database.backend.implementation.MySQL.MySQLLogUserVisit; -import org.linkahead.server.database.backend.implementation.MySQL.MySQLRegisterSubDomain; import org.linkahead.server.database.backend.implementation.MySQL.MySQLRetrieveAll; import org.linkahead.server.database.backend.implementation.MySQL.MySQLRetrieveAllUncheckedFiles; import org.linkahead.server.database.backend.implementation.MySQL.MySQLRetrieveDatatypes; @@ -106,7 +105,6 @@ import org.linkahead.server.database.backend.interfaces.IsSubTypeImpl; import org.linkahead.server.database.backend.interfaces.ListRolesImpl; import org.linkahead.server.database.backend.interfaces.ListUsersImpl; import org.linkahead.server.database.backend.interfaces.LogUserVisitImpl; -import org.linkahead.server.database.backend.interfaces.RegisterSubDomainImpl; import org.linkahead.server.database.backend.interfaces.RetrieveAllImpl; import org.linkahead.server.database.backend.interfaces.RetrieveAllUncheckedFilesImpl; import org.linkahead.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 b794528a173a8c1d067f7d6bf78d98a92a807781..0000000000000000000000000000000000000000 --- a/src/main/java/org/caosdb/server/database/DatabaseUtils.java +++ /dev/null @@ -1,376 +0,0 @@ -/* - * ** header v3.0 - * This file is a part of the LinkAhead 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.linkahead.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.linkahead.server.database.proto.FlatProperty; -import org.linkahead.server.database.proto.ProtoProperty; -import org.linkahead.server.database.proto.SparseEntity; -import org.linkahead.server.database.proto.VerySparseEntity; -import org.linkahead.server.datatype.AbstractCollectionDatatype; -import org.linkahead.server.datatype.CollectionValue; -import org.linkahead.server.datatype.IndexedSingleValue; -import org.linkahead.server.datatype.ReferenceValue; -import org.linkahead.server.datatype.SingleValue; -import org.linkahead.server.entity.EntityID; -import org.linkahead.server.entity.EntityInterface; -import org.linkahead.server.entity.Message; -import org.linkahead.server.entity.RetrieveEntity; -import org.linkahead.server.entity.Role; -import org.linkahead.server.entity.StatementStatus; -import org.linkahead.server.entity.wrapper.Domain; -import org.linkahead.server.entity.wrapper.Parent; -import org.linkahead.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)) { - // if p has no sub-properties, just add it - } else { - stage1Inserts.add(makeReplacement(p)); - } - processSubPropertiesStage1(stage1Inserts, p); - } - - public static int deriveStage2Inserts( - final List<EntityInterface> stage2Inserts, final List<EntityInterface> stage1Inserts) { - int domainCount = 0; - for (final EntityInterface p : stage1Inserts) { - if (p.hasRole() && p.getRole() == Role.Domain) { - domainCount++; - } - if (!p.hasReplacement() && p.hasProperties()) { - stage2Inserts.addAll(p.getProperties()); - } - } - return domainCount; - } - - private static boolean hasUniquePropertyId(final EntityInterface p, final EntityInterface e) { - final EntityID id = p.getId(); - for (final EntityInterface p2 : e.getProperties()) { - if (Objects.equal(p2.getId(), id) && p2 != p) { - return false; - } - } - return true; - } - - private static void processSubPropertiesStage1( - final List<EntityInterface> stage1Inserts, final EntityInterface p) { - for (final EntityInterface subP : p.getProperties()) { - if (subP.hasProperties()) { - stage1Inserts.add(makeReplacement(subP)); - processSubPropertiesStage1(stage1Inserts, subP); - } - } - } - - public static void parseFromSparseEntities(final EntityInterface e, final SparseEntity spe) { - if (spe != null) { - e.parseSparseEntity(spe); - } - } - - public static void parseOverrides(final List<FlatProperty> properties, final ResultSet rs) - throws SQLException { - while (rs.next()) { - final int property_id = rs.getInt("property_id"); - for (final FlatProperty p : properties) { - if (p.id == property_id) { - final String name = bytes2UTF8(rs.getBytes("name_override")); - if (name != null) { - p.name = name; - } - final String desc = bytes2UTF8(rs.getBytes("desc_override")); - if (desc != null) { - p.desc = desc; - } - final String type = bytes2UTF8(rs.getBytes("type_override")); - if (type != null) { - p.type = type; - } - final String coll = bytes2UTF8(rs.getBytes("collection_override")); - if (coll != null) { - p.collection = coll; - } - } - } - } - } - - public static List<FlatProperty> parsePropertyResultset(final ResultSet rs) throws SQLException { - final ArrayList<FlatProperty> ret = new ArrayList<FlatProperty>(); - while (rs.next()) { - final FlatProperty fp = new FlatProperty(); - fp.id = rs.getInt("PropertyID"); - fp.value = bytes2UTF8(rs.getBytes("PropertyValue")); - fp.status = bytes2UTF8(rs.getBytes("PropertyStatus")); - fp.idx = rs.getInt("PropertyIndex"); - ret.add(fp); - } - return ret; - } - - /** - * Helper function for parsing MySQL results. - * - * <p>This function creates SparseEntities and parses only the name, the role and the acl of an - * entity. - * - * <p>Never returns null. - */ - public static SparseEntity parseNameRoleACL(final ResultSet rs) throws SQLException { - final SparseEntity ret = new SparseEntity(); - ret.role = bytes2UTF8(rs.getBytes("EntityRole")); - ret.name = bytes2UTF8(rs.getBytes("EntityName")); - ret.acl = bytes2UTF8(rs.getBytes("ACL")); - return ret; - } - - /** - * Helper function for parsing MySQL results. - * - * <p>This function creates SparseEntities and parses all fields which belong to a SparseEntity: - * id, name, role, acl, description, datatype, and the file properties. - * - * <p>Never returns null. - */ - public static SparseEntity parseEntityResultSet(final ResultSet rs) throws SQLException { - final SparseEntity ret = parseNameRoleACL(rs); - ret.id = rs.getInt("EntityID"); - ret.description = bytes2UTF8(rs.getBytes("EntityDesc")); - ret.datatype = bytes2UTF8(rs.getBytes("Datatype")); - ret.collection = bytes2UTF8(rs.getBytes("Collection")); - - ret.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 8c29c00b0b413626b876559a6847157ced3fad5d..c544f2c5fce0fff92a124ac5b71f0838f24131b0 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.linkahead.server.database.DatabaseUtils; import org.linkahead.server.database.access.Access; import org.linkahead.server.database.backend.interfaces.GetAllNamesImpl; import org.linkahead.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 ece5ffe3e7faadaef512132b3df0960e783a1bf6..60dc5d3c31f0acfc0e932b97b0bf3ba1aea71e53 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.linkahead.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.linkahead.server.database.access.Access; import org.linkahead.server.database.backend.interfaces.InsertEntityPropertiesImpl; import org.linkahead.server.database.exceptions.IntegrityException; import org.linkahead.server.database.exceptions.TransactionException; import org.linkahead.server.database.proto.FlatProperty; +import org.linkahead.server.datatype.AbstractCollectionDatatype; import org.linkahead.server.datatype.AbstractDatatype.Table; +import org.linkahead.server.datatype.CollectionValue; +import org.linkahead.server.datatype.IndexedSingleValue; +import org.linkahead.server.datatype.ReferenceValue; +import org.linkahead.server.datatype.SingleValue; import org.linkahead.server.entity.EntityID; +import org.linkahead.server.entity.EntityInterface; +import org.linkahead.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 e70f21dfbb14504702c90ad9155a1152f48f2ba7..62bde118b4c1656376c146e4a278cda5bf6e5978 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.linkahead.server.database.DatabaseUtils; import org.linkahead.server.database.access.Access; import org.linkahead.server.database.backend.interfaces.InsertSparseEntityImpl; import org.linkahead.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 c6db34940df37e752af7dd520af93655a9aa7cb3..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 LinkAhead 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.linkahead.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.linkahead.server.database.access.Access; -import org.linkahead.server.database.backend.interfaces.RegisterSubDomainImpl; -import org.linkahead.server.database.exceptions.TransactionException; -import org.linkahead.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 d7b38b22014dbc5df1caee835c846d06211121f7..f82082d6460c0ee201b48d71a92b3d8392805f23 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.linkahead.server.database.DatabaseUtils; import org.linkahead.server.database.access.Access; import org.linkahead.server.database.backend.interfaces.RetrieveAllImpl; import org.linkahead.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 7ac9157208a982b19f8b6960e08229b57778a285..d3f41cc8f20b7c17f52e7faa890a3029f450a857 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.linkahead.server.database.backend.implementation.MySQL; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; -import java.util.ArrayList; -import org.linkahead.server.database.DatabaseUtils; +import java.util.List; import org.linkahead.server.database.access.Access; import org.linkahead.server.database.backend.interfaces.RetrieveDatatypesImpl; import org.linkahead.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 86ddd784093c6e4148cf2855073d37215678489e..5fdb944a345fc50ae2f4f73e87015c8c27ec8875 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.linkahead.server.database.DatabaseUtils; +import java.util.LinkedList; import org.linkahead.server.database.access.Access; import org.linkahead.server.database.backend.interfaces.RetrieveParentsImpl; import org.linkahead.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 9c5bbc7b9a5ea6883110e25b175c591ab05ad65f..144fb524dca11673f817ce8804d15eedf71920c6 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.linkahead.server.database.backend.implementation.MySQL; -import static org.linkahead.server.database.DatabaseUtils.bytes2UTF8; +import static org.linkahead.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 62b887c37ad22a174296557da7d0a3f48201833e..d9ebb771c1cde96cabf9783ecdcbee0692d58790 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.linkahead.server.database.DatabaseUtils; import org.linkahead.server.database.access.Access; import org.linkahead.server.database.backend.interfaces.RetrievePropertiesImpl; import org.linkahead.server.database.exceptions.TransactionException; -import org.linkahead.server.database.proto.FlatProperty; import org.linkahead.server.database.proto.ProtoProperty; import org.linkahead.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 9c45352b67bb544e88ba08a67cc73ee9b2e351a5..0ff92298117aefa247e2a450e56fbd03c2e3cd98 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.linkahead.server.database.DatabaseUtils; import org.linkahead.server.database.access.Access; import org.linkahead.server.database.backend.interfaces.RetrieveSparseEntityImpl; import org.linkahead.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 11f0b44435f5952d1f49c6c3db4d7e75153d8661..85d9ac262a9f68976e21457df609902ec5b4a7f0 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.linkahead.server.database.backend.implementation.MySQL; -import static org.linkahead.server.database.DatabaseUtils.bytes2UTF8; +import static org.linkahead.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 99c012c4e4e3e1d3f271f97424ec14da369ed1a8..d7c3b9f66213d557a83cd586de566781d990560b 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.linkahead.server.database.DatabaseUtils; import org.linkahead.server.database.access.Access; import org.linkahead.server.database.backend.interfaces.RetrieveVersionHistoryImpl; import org.linkahead.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 6a830302b23dabb5a4472aa354db2577abfe39cc..492ab7cbc3fe860685369f22937cfffcc282f734 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.linkahead.server.database.DatabaseUtils; import org.linkahead.server.database.access.Access; import org.linkahead.server.database.backend.interfaces.UpdateSparseEntityImpl; import org.linkahead.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 1a5a7d94a4b5b069f1e2d11d9a5a2cfb9383a7e3..a7f0888b79f9e84cc300a93c5b0343c30ca191e0 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.linkahead.server.database.backend.interfaces; -import org.linkahead.server.database.exceptions.TransactionException; -import org.linkahead.server.database.proto.FlatProperty; -import org.linkahead.server.datatype.AbstractDatatype.Table; -import org.linkahead.server.entity.EntityID; +import org.linkahead.server.entity.EntityInterface; import org.linkahead.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 ce3d7f31d6b3b9b3d59fe6198ffc63ac5e8af566..c3d94ce99cf206223fc4b7174ff66b02d7fc43c4 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.linkahead.server.database.backend.interfaces; -import java.util.ArrayList; +import java.util.List; import org.linkahead.server.database.exceptions.TransactionException; import org.linkahead.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 70b0f68e4d7edaaf9812c3278d8298de58eb3fea..6007efb36c61796f51e4e827dd53e13bb808072c 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.linkahead.server.database.backend.interfaces; -import java.util.ArrayList; +import java.util.LinkedList; import org.linkahead.server.database.exceptions.TransactionException; import org.linkahead.server.database.proto.VerySparseEntity; import org.linkahead.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 0a77096cdb314a7e61c0a0a0c0b52d0d667f2230..f65ff2d9e218f80ecab149d37a2f861434fa2119 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.linkahead.server.database.backend.interfaces; -import java.util.ArrayList; +import java.util.LinkedList; import org.linkahead.server.database.exceptions.TransactionException; import org.linkahead.server.database.proto.ProtoProperty; import org.linkahead.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 ce880d6c81ae013e745ad2c1b5a39810b449670b..c0cbcc7e875101b43936f2628ce25803f529b001 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.linkahead.server.database.backend.transaction; -import java.util.ArrayList; -import java.util.Deque; -import java.util.Iterator; -import java.util.List; import org.linkahead.server.database.BackendTransaction; -import org.linkahead.server.database.DatabaseUtils; import org.linkahead.server.database.backend.interfaces.InsertEntityPropertiesImpl; import org.linkahead.server.database.exceptions.TransactionException; -import org.linkahead.server.database.proto.FlatProperty; -import org.linkahead.server.datatype.AbstractCollectionDatatype; -import org.linkahead.server.datatype.AbstractDatatype.Table; -import org.linkahead.server.datatype.CollectionValue; -import org.linkahead.server.datatype.IndexedSingleValue; -import org.linkahead.server.datatype.SingleValue; -import org.linkahead.server.entity.EntityID; import org.linkahead.server.entity.EntityInterface; -import org.linkahead.server.entity.Role; -import org.linkahead.server.entity.StatementStatus; -import org.linkahead.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 37775843398134e56ae418dd796b19b407d50823..1c16626fd52f9309ba5c6b3a1f39dc2c0680b1ea 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 a39993328f6c82a5de744aa97e50065d1cb57a61..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 LinkAhead 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.linkahead.server.database.backend.transaction; - -import java.util.Deque; -import org.linkahead.server.database.BackendTransaction; -import org.linkahead.server.database.backend.interfaces.RegisterSubDomainImpl; -import org.linkahead.server.database.exceptions.TransactionException; -import org.linkahead.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 1d6d88adcb7d8bd0f52e7f182863ef784690f153..82698207825f8db4200b81a7a173d938836ed34e 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.linkahead.server.database.backend.transaction; -import java.util.ArrayList; +import java.util.List; import org.linkahead.server.database.BackendTransaction; import org.linkahead.server.database.backend.interfaces.RetrieveDatatypesImpl; import org.linkahead.server.database.exceptions.TransactionException; @@ -35,7 +35,7 @@ import org.linkahead.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 0e3738d2346eb32295e33276194f29bf22c6fa30..49aa672ac69e8439a7de470b36a3474934512971 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.linkahead.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.linkahead.server.caching.Cache; import org.linkahead.server.database.CacheableBackendTransaction; -import org.linkahead.server.database.DatabaseUtils; import org.linkahead.server.database.backend.interfaces.RetrieveParentsImpl; import org.linkahead.server.database.exceptions.TransactionException; import org.linkahead.server.database.proto.VerySparseEntity; +import org.linkahead.server.entity.EntityID; import org.linkahead.server.entity.EntityInterface; +import org.linkahead.server.entity.RetrieveEntity; +import org.linkahead.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.linkahead.server.entity.EntityInterface; // See also a failing test in linkahead-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 a2f099fae7accca49ab45b47ffb4d9e89eec06f2..c1b2ae94138b9573a37912e0387547c271e190a6 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.linkahead.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.linkahead.server.caching.Cache; import org.linkahead.server.database.CacheableBackendTransaction; -import org.linkahead.server.database.DatabaseUtils; +import org.linkahead.server.database.backend.implementation.MySQL.DatabaseUtils; import org.linkahead.server.database.backend.interfaces.RetrievePropertiesImpl; import org.linkahead.server.database.exceptions.TransactionException; import org.linkahead.server.database.proto.ProtoProperty; @@ -37,11 +38,11 @@ import org.linkahead.server.entity.Role; import org.linkahead.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 7ad7b86966de5a065a4cd4f805aace7197904fc4..a6fbfdf22925d90376960a14b9363886762a62d2 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.linkahead.server.database.backend.transaction; import org.apache.commons.jcs.access.behavior.ICacheAccess; import org.linkahead.server.caching.Cache; import org.linkahead.server.database.CacheableBackendTransaction; -import org.linkahead.server.database.DatabaseUtils; import org.linkahead.server.database.backend.interfaces.RetrieveSparseEntityImpl; import org.linkahead.server.database.exceptions.TransactionException; import org.linkahead.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 d5a9247833045e3dbe3b557277e7b4c51f7cbbf4..d0dae166736a992f4aaacac9bd273c7e00854a35 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.linkahead.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 612f718effcf2ff749eb8d4c9776bdc5b3a763e8..12de67ca475322b1d6de3d1520f180d68f0d587b 100644 --- a/src/main/java/org/caosdb/server/entity/Entity.java +++ b/src/main/java/org/caosdb/server/entity/Entity.java @@ -34,6 +34,32 @@ import org.apache.shiro.SecurityUtils; import org.apache.shiro.authz.AuthorizationException; import org.apache.shiro.authz.Permission; import org.apache.shiro.subject.Subject; +import org.linkahead.server.CaosDBException; +import org.linkahead.server.accessControl.Principal; +import org.linkahead.server.database.proto.SparseEntity; +import org.linkahead.server.database.proto.VerySparseEntity; +import org.linkahead.server.datatype.AbstractCollectionDatatype; +import org.linkahead.server.datatype.AbstractDatatype; +import org.linkahead.server.datatype.CollectionValue; +import org.linkahead.server.datatype.GenericValue; +import org.linkahead.server.datatype.ReferenceDatatype; +import org.linkahead.server.datatype.Value; +import org.linkahead.server.entity.Message.MessageType; +import org.linkahead.server.entity.container.ParentContainer; +import org.linkahead.server.entity.container.PropertyContainer; +import org.linkahead.server.entity.wrapper.Parent; +import org.linkahead.server.entity.wrapper.Property; +import org.linkahead.server.entity.xml.EntityToElementStrategy; +import org.linkahead.server.entity.xml.SerializeFieldStrategy; +import org.linkahead.server.entity.xml.ToElementStrategy; +import org.linkahead.server.entity.xml.ToElementable; +import org.linkahead.server.permissions.EntityACL; +import org.linkahead.server.query.Query.Selection; +import org.linkahead.server.utils.AbstractObservable; +import org.linkahead.server.utils.EntityStatus; +import org.linkahead.server.utils.ServerMessages; +import org.linkahead.server.utils.TransactionLogMessage; +import org.linkahead.unit.Unit; import org.jdom2.Element; import org.linkahead.server.LinkAheadException; import org.linkahead.server.accessControl.Principal; @@ -422,7 +448,7 @@ public abstract class Entity extends AbstractObservable implements EntityInterfa } } - private StatementStatus statementStatus = null; + private StatementStatusInterface statementStatus = null; /** * statementStatus getter. @@ -430,7 +456,7 @@ public abstract class Entity extends AbstractObservable implements EntityInterfa * @return */ @Override - public StatementStatus getStatementStatus() { + public StatementStatusInterface getStatementStatus() { return this.statementStatus; } @@ -440,7 +466,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 +635,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 +875,6 @@ public abstract class Entity extends AbstractObservable implements EntityInterfa } } - @Override - public void setReplacement(final Domain d) { - this.replacement = d; - } - - @Override - public boolean hasReplacement() { - return this.replacement != null; - } - - @Override - public Domain getReplacement() { - return this.replacement; - } - - private Domain replacement = null; - private final HashMap<String, String> flags = new HashMap<String, String>(); @Override @@ -1004,7 +1014,7 @@ public abstract class Entity extends AbstractObservable implements EntityInterfa @Override public boolean skipJob() { - return false; + return this.entityStatus == EntityStatus.IGNORE; } @Override diff --git a/src/main/java/org/caosdb/server/entity/EntityInterface.java b/src/main/java/org/caosdb/server/entity/EntityInterface.java index f4ed2947d64cb0eabfdc6f19932683646852d638..e0845d46bc4facd9540ee438eadc3bfc6cd2aedd 100644 --- a/src/main/java/org/caosdb/server/entity/EntityInterface.java +++ b/src/main/java/org/caosdb/server/entity/EntityInterface.java @@ -25,6 +25,21 @@ package org.linkahead.server.entity; import java.util.List; import org.apache.shiro.authz.Permission; import org.apache.shiro.subject.Subject; +import org.linkahead.server.database.proto.SparseEntity; +import org.linkahead.server.database.proto.VerySparseEntity; +import org.linkahead.server.datatype.AbstractDatatype; +import org.linkahead.server.datatype.Value; +import org.linkahead.server.entity.container.ParentContainer; +import org.linkahead.server.entity.container.PropertyContainer; +import org.linkahead.server.entity.wrapper.Parent; +import org.linkahead.server.entity.wrapper.Property; +import org.linkahead.server.entity.xml.SerializeFieldStrategy; +import org.linkahead.server.entity.xml.ToElementable; +import org.linkahead.server.jobs.JobTarget; +import org.linkahead.server.permissions.EntityACL; +import org.linkahead.server.utils.Observable; +import org.linkahead.server.utils.TransactionLogMessage; +import org.linkahead.unit.Unit; import org.jdom2.Element; import org.linkahead.server.database.proto.SparseEntity; import org.linkahead.server.database.proto.VerySparseEntity; @@ -58,9 +73,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 +142,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 f0bf6307dbba7abf668382546a1c300ea468079c..92840787fe140524bd2a05f01a7f019b3452fe2b 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.linkahead.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 ffe32c3ceac798ac696a5a6cd788712f4a61bdcb..102802fc27217b581fcfb0304acb2ac82457b039 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 LinkAhead 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.linkahead.server.database.backend.interfaces; - -import java.util.Deque; -import org.linkahead.server.database.exceptions.TransactionException; -import org.linkahead.server.entity.EntityID; +package org.linkahead.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.linkahead.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 afbcebc23a913ada5da735070d1bc83064879078..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 LinkAhead 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.linkahead.server.entity.wrapper; - -import org.linkahead.server.datatype.AbstractDatatype; -import org.linkahead.server.datatype.Value; -import org.linkahead.server.entity.Entity; -import org.linkahead.server.entity.EntityInterface; -import org.linkahead.server.entity.Role; -import org.linkahead.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.linkahead.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 5030d62f6ba4fda8212e66a972193b1e1b8a13da..eb411869f6f5d9cbe699adda5248c892189c3e87 100644 --- a/src/main/java/org/caosdb/server/entity/wrapper/EntityWrapper.java +++ b/src/main/java/org/caosdb/server/entity/wrapper/EntityWrapper.java @@ -29,7 +29,6 @@ import java.util.Map; import java.util.Set; import org.apache.shiro.authz.Permission; import org.apache.shiro.subject.Subject; -import org.jdom2.Element; import org.linkahead.server.database.proto.SparseEntity; import org.linkahead.server.database.proto.VerySparseEntity; import org.linkahead.server.datatype.AbstractDatatype; @@ -39,7 +38,7 @@ import org.linkahead.server.entity.EntityInterface; import org.linkahead.server.entity.FileProperties; import org.linkahead.server.entity.Message; import org.linkahead.server.entity.Role; -import org.linkahead.server.entity.StatementStatus; +import org.linkahead.server.entity.StatementStatusInterface; import org.linkahead.server.entity.Version; import org.linkahead.server.entity.container.ParentContainer; import org.linkahead.server.entity.container.PropertyContainer; @@ -52,7 +51,7 @@ import org.linkahead.server.utils.EntityStatus; import org.linkahead.server.utils.Observer; import org.linkahead.server.utils.TransactionLogMessage; import org.linkahead.unit.Unit; - +import org.jdom2.Element; public abstract class EntityWrapper implements EntityInterface { protected EntityInterface entity; @@ -112,13 +111,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 +375,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 f6cfe025843231aa524cf26bb99edf7e8d6b8488..0755928ab621aff80404e7b3b13015f54ffcd897 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.linkahead.server.entity.wrapper; import org.apache.shiro.authz.AuthorizationException; import org.apache.shiro.authz.Permission; import org.apache.shiro.subject.Subject; -import org.linkahead.server.database.proto.FlatProperty; +import org.linkahead.server.database.proto.ProtoProperty; import org.linkahead.server.datatype.AbstractCollectionDatatype; +import org.linkahead.server.datatype.CollectionValue; import org.linkahead.server.datatype.GenericValue; import org.linkahead.server.entity.EntityID; import org.linkahead.server.entity.EntityInterface; +import org.linkahead.server.entity.RetrieveEntity; import org.linkahead.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 90a0a1b0a406da34d3b98a756b2af53382df0fd6..a98969a463f77b16e5d8a11b2f69bfbd173e19fd 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/ParentToElementStrategy.java b/src/main/java/org/caosdb/server/entity/xml/ParentToElementStrategy.java index 78c199437e7ecdf8fb2bb744a78887eed76a498c..6ce7c24b9157b3ff20cadb033c50d97b0b5efc3d 100644 --- a/src/main/java/org/caosdb/server/entity/xml/ParentToElementStrategy.java +++ b/src/main/java/org/caosdb/server/entity/xml/ParentToElementStrategy.java @@ -24,10 +24,9 @@ */ package org.linkahead.server.entity.xml; -import org.jdom2.Element; import org.linkahead.server.entity.EntityInterface; import org.linkahead.server.entity.wrapper.Parent; -import org.linkahead.server.utils.EntityStatus; +import org.jdom2.Element; /** * Generates a JDOM (XML) representation of an entity's parent. @@ -51,15 +50,4 @@ public class ParentToElementStrategy extends EntityToElementStrategy { } return element; } - - @Override - public Element addToElement( - final EntityInterface entity, - final Element element, - final SerializeFieldStrategy setFieldStrategy) { - if (entity.getEntityStatus() != EntityStatus.IGNORE) { - element.addContent(toElement(entity, setFieldStrategy)); - } - return element; - } } diff --git a/src/main/java/org/caosdb/server/entity/xml/PropertyToElementStrategy.java b/src/main/java/org/caosdb/server/entity/xml/PropertyToElementStrategy.java index 1c4e437e138d9d87b324dc6aff3697dbd586acd5..ab5b60b8b7313622cc936bbb3c2349954e1c86a6 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 b778fc220130003983565c8a23291627662a398b..b83226687f8f2a183ab29e4b7e85b44b51338840 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.linkahead.server.entity.MagicTypes; import org.linkahead.server.entity.Message; import org.linkahead.server.entity.Role; import org.linkahead.server.entity.StatementStatus; +import org.linkahead.server.entity.StatementStatusInterface; import org.linkahead.server.entity.container.ParentContainer; import org.linkahead.server.entity.container.RetrieveContainer; import org.linkahead.server.entity.wrapper.Property; @@ -298,6 +299,8 @@ public class LinkAheadToGrpcConverters { } 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 LinkAheadToGrpcConverters { return convertScalarValue(v.getWrapped()).build(); } - private Importance convert(final StatementStatus statementStatus) { - switch (statementStatus) { + private Importance convert(final StatementStatusInterface statementStatus) { + switch ((StatementStatus) statementStatus) { case FIX: return Importance.IMPORTANCE_FIX; case OBLIGATORY: @@ -707,8 +710,7 @@ public class LinkAheadToGrpcConverters { return null; } - private org.linkahead.api.entity.v1.Value.Builder getSelectedValue( - Selection s, EntityInterface e) { + org.linkahead.api.entity.v1.Value.Builder getSelectedValue(Selection s, EntityInterface e) { org.linkahead.api.entity.v1.Value.Builder result = null; String selector = s.getSelector(); result = handleSpecialSelectors(selector, e); @@ -739,8 +741,9 @@ public class LinkAheadToGrpcConverters { 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 3105ecee5985288e77d8c7dcc0c59929cc9f0f96..0d6ce4631f7b89bee408b1c94e0107bf584765de 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.linkahead.server.grpc; +import io.grpc.Attributes.Key; import io.grpc.Context; import io.grpc.Contexts; import io.grpc.Metadata; @@ -7,18 +8,70 @@ import io.grpc.ServerCall; import io.grpc.ServerCall.Listener; import io.grpc.ServerCallHandler; import io.grpc.ServerInterceptor; +import java.net.SocketAddress; +import org.caosdb.server.CaosDBServer; +import org.caosdb.server.ServerProperties; +import org.restlet.routing.Template; +import org.restlet.util.Resolver; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +class CallResolver<ReqT, RespT> extends Resolver<String> { + + private ServerCall<ReqT, RespT> call; + private Metadata headers; + + public static final Metadata.Key<String> KEY_USER_AGENT = + Metadata.Key.of("user-agent", Metadata.ASCII_STRING_MARSHALLER); + public static final Key<SocketAddress> KEY_LOCAL_ADDRESS = io.grpc.Grpc.TRANSPORT_ATTR_LOCAL_ADDR; + public static final Key<SocketAddress> KEY_REMOTE_ADDRESS = + io.grpc.Grpc.TRANSPORT_ATTR_REMOTE_ADDR; + + public CallResolver( + ServerCall<ReqT, RespT> call, Metadata headers, ServerCallHandler<ReqT, RespT> next) { + this.call = call; + this.headers = headers; + } + + @Override + public String resolve(String name) { + switch (name) { + case "user-agent": + return headers.get(KEY_USER_AGENT); + case "remote-address": + return call.getAttributes().get(KEY_REMOTE_ADDRESS).toString(); + case "local-address": + return call.getAttributes().get(KEY_LOCAL_ADDRESS).toString(); + case "method": + return call.getMethodDescriptor().getFullMethodName(); + default: + break; + } + return null; + } +} + public class LoggingInterceptor implements ServerInterceptor { + private Template template; + + public LoggingInterceptor() { + String format = CaosDBServer.getServerProperty(ServerProperties.KEY_GRPC_RESPONSE_LOG_FORMAT); + if ("OFF".equalsIgnoreCase(format)) { + this.template = null; + } else if (format != null) { + this.template = new Template(format); + } + } + private static final Logger logger = LoggerFactory.getLogger(LoggingInterceptor.class.getName()); @Override public <ReqT, RespT> Listener<ReqT> interceptCall( ServerCall<ReqT, RespT> call, Metadata headers, ServerCallHandler<ReqT, RespT> next) { - - logger.info(call.getMethodDescriptor().getFullMethodName() + " - " + call.getAttributes()); + if (template != null) { + logger.info(template.format(new CallResolver<ReqT, RespT>(call, headers, next))); + } return Contexts.interceptCall(Context.current(), call, headers, next); } } diff --git a/src/main/java/org/caosdb/server/jobs/core/ExecuteQuery.java b/src/main/java/org/caosdb/server/jobs/core/ExecuteQuery.java index 6aa6421526c2ac82f8f48bcff36cacd4605a46a2..ce0ca985f9b0b9a76f2e933eb163f1b765bcc443 100644 --- a/src/main/java/org/caosdb/server/jobs/core/ExecuteQuery.java +++ b/src/main/java/org/caosdb/server/jobs/core/ExecuteQuery.java @@ -31,15 +31,20 @@ import org.linkahead.server.jobs.JobAnnotation; import org.linkahead.server.jobs.TransactionStage; import org.linkahead.server.query.Query; import org.linkahead.server.query.Query.ParsingException; +import org.linkahead.server.transaction.Retrieve; +import org.linkahead.server.utils.EntityStatus; import org.linkahead.server.utils.ServerMessages; -@JobAnnotation(flag = "query", stage = TransactionStage.INIT) +@JobAnnotation(flag = "query", stage = TransactionStage.INIT, transaction = Retrieve.class) public class ExecuteQuery extends FlagJob { @Override protected void job(final String value) { if (value != null) { + // run paging job first + getTransaction().getSchedule().runJob(null, Paging.class); + final Query queryInstance = new Query(value, getTransaction().getTransactor(), getContainer()); getContainer().setQuery(queryInstance); @@ -53,8 +58,24 @@ public class ExecuteQuery extends FlagJob { .addMessage(new Message(MessageType.Info, (MessageCode) null, e.getMessage())); } getContainer().addMessage(queryInstance); + + int startIndex = 0; + int endIndex = getContainer().size(); + + if (((Retrieve) getTransaction()).hasPaging()) { + Retrieve.Paging paging = ((Retrieve) getTransaction()).getPaging(); + startIndex = Math.min(getContainer().size(), paging.startIndex); + endIndex = Math.min(getContainer().size(), paging.endIndex); + } + + int ii = 0; for (final EntityInterface entity : getContainer()) { - getTransaction().getSchedule().addAll(loadJobs(entity, getTransaction())); + if (ii >= startIndex && ii < endIndex) { + getTransaction().getSchedule().addAll(loadJobs(entity, getTransaction())); + } else { + entity.setEntityStatus(EntityStatus.IGNORE); + } + ii++; } } } diff --git a/src/main/java/org/caosdb/server/jobs/core/Paging.java b/src/main/java/org/caosdb/server/jobs/core/Paging.java index dc1da77b00675310add7583a0ccf9db9ae1d7bf8..b390cf32f5700a4cbab637de834027f8ffe0697e 100644 --- a/src/main/java/org/caosdb/server/jobs/core/Paging.java +++ b/src/main/java/org/caosdb/server/jobs/core/Paging.java @@ -31,7 +31,7 @@ import org.linkahead.server.jobs.TransactionStage; import org.linkahead.server.transaction.Retrieve; import org.linkahead.server.utils.EntityStatus; -@JobAnnotation(flag = "P", stage = TransactionStage.PRE_TRANSACTION) +@JobAnnotation(flag = "P", stage = TransactionStage.INIT, transaction = Retrieve.class) public class Paging extends FlagJob { public static final int DEFAULT_LENGTH = 100; @@ -42,20 +42,24 @@ public class Paging extends FlagJob { protected void job(final String value) { if (getTransaction() instanceof Retrieve) { if (value != null) { - int index1 = DEFAULT_INDEX; - int index2 = index1 + DEFAULT_LENGTH; + int startIndex = DEFAULT_INDEX; + int endIndex = startIndex + DEFAULT_LENGTH; final Matcher m = pattern.matcher(value); if (m.matches()) { if (m.group(1) != null) { - index1 = Integer.parseInt(m.group(1)); + startIndex = Integer.parseInt(m.group(1)); } if (m.group(2) != null) { - index2 = index1 + Integer.parseInt(m.group(2)); + endIndex = startIndex + Integer.parseInt(m.group(2)); } } + + // this info may be used by other jobs + ((Retrieve) getTransaction()).setPaging(startIndex, endIndex); + int i = 0; for (final EntityInterface e : getContainer()) { - if (i >= index2 || i < index1) { + if (i >= endIndex || i < startIndex) { // do not retrieve this entity e.setEntityStatus(EntityStatus.IGNORE); } diff --git a/src/main/java/org/caosdb/server/jobs/core/RetrieveAllJob.java b/src/main/java/org/caosdb/server/jobs/core/RetrieveAllJob.java index 09550685ba406737db7eee89cafada227380e6f9..8112dcda3e7a8cf290d7ba51e9266100ead23997 100644 --- a/src/main/java/org/caosdb/server/jobs/core/RetrieveAllJob.java +++ b/src/main/java/org/caosdb/server/jobs/core/RetrieveAllJob.java @@ -27,19 +27,43 @@ import org.linkahead.server.entity.EntityInterface; import org.linkahead.server.jobs.FlagJob; import org.linkahead.server.jobs.JobAnnotation; import org.linkahead.server.jobs.TransactionStage; +import org.linkahead.server.transaction.Retrieve; +import org.linkahead.server.utils.EntityStatus; -@JobAnnotation(flag = "all", stage = TransactionStage.INIT) +@JobAnnotation(flag = "all", stage = TransactionStage.INIT, transaction = Retrieve.class) public class RetrieveAllJob extends FlagJob { @Override protected void job(String value) { if (getContainer().isEmpty()) { + // run paging job first + getTransaction().getSchedule().runJob(null, Paging.class); + if (value == null) { value = "ENTITY"; } + execute(new RetrieveAll(getContainer(), value)); + + int startIndex = 0; + int endIndex = getContainer().size(); + + if (((Retrieve) getTransaction()).hasPaging()) { + startIndex = + Math.min(getContainer().size(), ((Retrieve) getTransaction()).getPaging().startIndex); + endIndex = + Math.min(getContainer().size(), ((Retrieve) getTransaction()).getPaging().endIndex); + } + + // only add jobs for those which are on this page + int ii = 0; for (EntityInterface entity : getContainer()) { - getTransaction().getSchedule().addAll(loadJobs(entity, getTransaction())); + if (ii >= startIndex && ii < endIndex) { + getTransaction().getSchedule().addAll(loadJobs(entity, getTransaction())); + } else { + entity.setEntityStatus(EntityStatus.IGNORE); + } + ii++; } } } diff --git a/src/main/java/org/caosdb/server/logging/log4j/CustomConfigurationFactory.java b/src/main/java/org/caosdb/server/logging/log4j/CustomConfigurationFactory.java index 40daa76d70245e9c6566660b4f07219f26394445..0db1ad679c5641be52619aa4c11b4e08ce3425ce 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 2b5705f4f708fd6074bd2f92ccac4c9c91a5d007..3f29ec03d793472daefd0aa7f0452e9a20363b77 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.linkahead.server.query; import static java.sql.Types.VARCHAR; -import static org.linkahead.server.database.DatabaseUtils.bytes2UTF8; +import static org.linkahead.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 54a1786a237b6c1e2d76434f99537f8701223f9d..f6e199cf675ff52355dc887e281f7e91474a4363 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.linkahead.server.query; -import static org.linkahead.server.database.DatabaseUtils.bytes2UTF8; +import static org.linkahead.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 2d188c68a803dffdd611c3e3f3878d6d6a39ba16..ff4dd8e6ddcdf2511b099c3cf4071f34e545814f 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.linkahead.server.query; -import static org.linkahead.server.database.DatabaseUtils.bytes2UTF8; +import static org.linkahead.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 b5cc452cbf398099db8c1d36193a083e5bdee565..7aad2caee4914bb4a9f221c14b0a4f455fd28d7e 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.linkahead.server.query; -import static org.linkahead.server.database.DatabaseUtils.bytes2UTF8; +import static org.linkahead.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 0612e8e30d736e8cd028a4b9b21e33d2dc66b550..8374b2d83c6c07a3f4a63f26aeefbeb4decf932f 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.linkahead.server.query; import static java.sql.Types.DOUBLE; import static java.sql.Types.INTEGER; import static java.sql.Types.VARCHAR; -import static org.linkahead.server.database.DatabaseUtils.bytes2UTF8; +import static org.linkahead.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 05ff13f067157565b26b12a78b724b13c705b63a..5ed794fce4c1524aa5e8d8866cf48d94170a2fd8 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.linkahead.server.query; -import static org.linkahead.server.database.DatabaseUtils.bytes2UTF8; +import static org.linkahead.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 4d2b7834538a4214b7ac1b188b57a1bb53ef3dc8..00ff7ab461b01398466b141169893b859a2f52d4 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.linkahead.server.query; import static java.sql.Types.VARCHAR; -import static org.linkahead.server.database.DatabaseUtils.bytes2UTF8; +import static org.linkahead.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 996cbe546ae066d213430a5dc23ecc8c0a9ee84e..69653780eeff21ec433ec01595d3880d9957b95c 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 AbstractLinkAheadServerResource { } 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 AbstractLinkAheadServerResource { } 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/transaction/Retrieve.java b/src/main/java/org/caosdb/server/transaction/Retrieve.java index 529da90e387cc0811c65089a4fb3b036675a6589..3f11f976d8367edf6ac433bcd3071d5e8b034716 100644 --- a/src/main/java/org/caosdb/server/transaction/Retrieve.java +++ b/src/main/java/org/caosdb/server/transaction/Retrieve.java @@ -121,4 +121,28 @@ public class Retrieve extends Transaction<RetrieveContainer> { public boolean logHistory() { return false; } + + public static class Paging { + public Paging(int startIndex, int endIndex) { + this.startIndex = startIndex; + this.endIndex = endIndex; + } + + public final int startIndex; + public final int endIndex; + } + + private Retrieve.Paging paging = null; + + public boolean hasPaging() { + return this.paging != null; + } + + public void setPaging(int startIndex, int endIndex) { + this.paging = new Retrieve.Paging(startIndex, endIndex); + } + + public Retrieve.Paging getPaging() { + return paging; + } } diff --git a/src/main/java/org/caosdb/server/transaction/Transaction.java b/src/main/java/org/caosdb/server/transaction/Transaction.java index bd27cb814ad62169a5efac57031b1aab55025b01..21ccb9dfb4cf049c69ed11348c8419fd3612248d 100644 --- a/src/main/java/org/caosdb/server/transaction/Transaction.java +++ b/src/main/java/org/caosdb/server/transaction/Transaction.java @@ -105,6 +105,7 @@ public abstract class Transaction<C extends TransactionContainer> extends Abstra this.schedule.addAll(Job.loadPermanentContainerJobs(this)); for (final EntityInterface e : getContainer()) { + if (e.skipJob()) continue; final List<Job> loadJobs = loadContainerFlags.loadJobs(e, this); this.schedule.addAll(loadJobs); diff --git a/src/main/java/org/caosdb/server/utils/ResultSetIterator.java b/src/main/java/org/caosdb/server/utils/ResultSetIterator.java index 3f465c6f39a82798c55ec1a8328526b57d92e409..42da71d9f7b183dc87b1273699c990c5d64375be 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.linkahead.server.utils; -import static org.linkahead.server.database.DatabaseUtils.bytes2UTF8; +import static org.linkahead.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 6f728114192c61f6958e564dc743c01c3a33b339..1d4c8a1808c18578e2fcf8b2d06f2fec7759ba84 100644 --- a/src/main/java/org/caosdb/server/utils/WebinterfaceUtils.java +++ b/src/main/java/org/caosdb/server/utils/WebinterfaceUtils.java @@ -61,6 +61,23 @@ public class WebinterfaceUtils { private long buildNumberDate; private static final Map<String, WebinterfaceUtils> instances = new HashMap<>(); + public static String getForwardedProto(Request request) { + String scheme = null; + String forwarded = request.getHeaders().getFirstValue("Forwarded", true); + if (forwarded != null) { + for (String directive : forwarded.split(";")) { + String[] s = directive.split("=", 2); + if (s.length == 2 && "proto".equalsIgnoreCase(s[0])) { + scheme = s[1]; + } + } + } + if (scheme == null) { + scheme = request.getHeaders().getFirstValue("X-Forwarded-Proto", true); + } + return scheme; + } + /** * Retrieve an instance of {@link WebinterfaceUtils} for the request. The instance can be shared * with other callers. @@ -70,7 +87,7 @@ public class WebinterfaceUtils { */ public static WebinterfaceUtils getInstance(Request request) { String hostStr = request.getHostRef().getHostIdentifier(); - String scheme = request.getHeaders().getFirstValue("X-Forwarded-Proto", true); + String scheme = getForwardedProto(request); if (scheme != null) { hostStr = hostStr.replaceFirst("^" + request.getHostRef().getScheme(), scheme); } diff --git a/src/test/docker/Dockerfile b/src/test/docker/Dockerfile index 19cf9dcd5cae9431ae0dc265870acbdfb1b3e650..f0ae05e65389f4b89cb0cb1373a7475b054490cd 100644 --- a/src/test/docker/Dockerfile +++ b/src/test/docker/Dockerfile @@ -6,15 +6,14 @@ RUN apt-get update && \ libtiff5-dev libjpeg-dev libopenjp2-7-dev zlib1g-dev \ libfreetype6-dev liblcms2-dev libwebp-dev tcl8.6-dev tk8.6-dev python3-tk \ libharfbuzz-dev libfribidi-dev libxcb1-dev \ - python3-pip screen libpam0g-dev unzip curl shunit2 \ - python3-sphinx + python3-pip screen libpam0g-dev unzip curl shunit2 -RUN pip3 install javasphinx recommonmark sphinx-rtd-theme sphinxcontrib-plantuml sphinx-a4doc +RUN apt-get install -y \ + libcairo2-dev + +RUN pip3 install sphinx javasphinx recommonmark sphinx-rtd-theme sphinxcontrib-plantuml sphinx-a4doc # Alternative, if javasphinx fails because python3-sphinx is too recent: # (`_l` not found): # -# git clone git@github.com:simgrid/javasphinx.git -# cd javasphinx -# git checkout 659209069603a -# pip3 install . +RUN git clone https://github.com/simgrid/javasphinx.git && cd javasphinx && git checkout 659209069603a && pip3 install . diff --git a/src/test/java/org/caosdb/server/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 80cd57580cb9c53ed63c0b171c4f12be196fcee3..a4cab3001217edbcf39254153232b41be6c8a44b 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,30 +20,41 @@ * * ** end header */ -package org.linkahead.server.database; +package org.linkahead.server.database.backend.implementation.MySQL; +import static org.linkahead.server.database.backend.implementation.MySQL.DatabaseUtils.deriveStage1Inserts; +import static org.linkahead.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 static org.linkahead.server.database.DatabaseUtils.deriveStage1Inserts; -import static org.linkahead.server.database.DatabaseUtils.deriveStage2Inserts; +import java.util.ArrayDeque; +import java.util.Deque; import java.util.LinkedList; -import org.junit.jupiter.api.Test; +import java.util.List; import org.linkahead.server.datatype.CollectionValue; import org.linkahead.server.datatype.GenericValue; +import org.linkahead.server.datatype.ReferenceValue; import org.linkahead.server.datatype.SingleValue; import org.linkahead.server.entity.Entity; import org.linkahead.server.entity.EntityID; -import org.linkahead.server.entity.EntityInterface; import org.linkahead.server.entity.InsertEntity; import org.linkahead.server.entity.RetrieveEntity; import org.linkahead.server.entity.Role; import org.linkahead.server.entity.StatementStatus; import org.linkahead.server.entity.wrapper.Property; +import org.junit.jupiter.api.Test; public class InsertTest { + private Deque<EntityID> registerReplacementIds(int count) { + Deque<EntityID> replacementIds = new ArrayDeque<>(); + for (int i = 1; i < count + 1; i++) { + replacementIds.add(new EntityID(-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 1002d242bfea7eccabf89f0ba3d065195118ce7a..8db47236ae03c6c21944b58c48216f77dc5cade8 100644 --- a/src/test/java/org/caosdb/server/grpc/CaosDBToGrpcConvertersTest.java +++ b/src/test/java/org/caosdb/server/grpc/CaosDBToGrpcConvertersTest.java @@ -1,13 +1,14 @@ package org.linkahead.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.junit.jupiter.api.Test; +import org.linkahead.api.entity.v1.Value.Builder; import org.linkahead.datetime.DateTimeFactory2; +import org.linkahead.server.datatype.CollectionValue; import org.linkahead.server.datatype.GenericValue; +import org.linkahead.server.datatype.ReferenceValue; import org.linkahead.server.datatype.Value; import org.linkahead.server.entity.EntityID; import org.linkahead.server.entity.FileProperties; @@ -18,6 +19,8 @@ import org.linkahead.server.entity.StatementStatus; import org.linkahead.server.entity.wrapper.Parent; import org.linkahead.server.entity.wrapper.Property; import org.linkahead.server.entity.xml.IdAndServerMessagesOnlyStrategy; +import org.linkahead.server.query.Query.Selection; +import org.junit.jupiter.api.Test; public class LinkAheadToGrpcConvertersTest { @@ -27,7 +30,9 @@ public class LinkAheadToGrpcConvertersTest { DateTimeFactory2 factory = new DateTimeFactory2(timeZone); LinkAheadToGrpcConverters converters = new LinkAheadToGrpcConverters(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 LinkAheadToGrpcConvertersTest { 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()); + } }