diff --git a/conf/core/jobs.csv b/conf/core/jobs.csv new file mode 100644 index 0000000000000000000000000000000000000000..a604e63b736ee5af04889d8ec0af356f76b7a119 --- /dev/null +++ b/conf/core/jobs.csv @@ -0,0 +1,112 @@ +# +# This file is a part of the CaosDB Project. +# +# Copyright (C) 2018 Research Group Biomedical Physics, +# Max-Planck-Institute for Dynamics and Self-Organization Göttingen +# Copyright (C) 2021 Timm Fitsche <t.fitschen@indiscale.com> +# Copyright (C) 2021 IndiScale GmbH <info@indiscale.com> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. +# + +# Five columns: +# DOMAIN_ID, ENTITY_ID, TRANSACTION, JOB, MODE + +# general rules +0,0,INSERT,CheckPropValid,MUST +0,0,INSERT,CheckParValid,MUST +0,0,INSERT,CheckParOblPropPresent,MUST +0,0,INSERT,CheckValueParsable,MUST +0,0,UPDATE,CheckPropValid,MUST +0,0,UPDATE,CheckParValid,MUST +0,0,UPDATE,CheckParOblPropPresent,MUST +0,0,UPDATE,CheckValueParsable,MUST +0,0,DELETE,CheckReferenceDependencyExistent,MUST +0,0,DELETE,CheckChildDependencyExistent,MUST + + +# role specific rules +## recordtype rules +0,1,INSERT,CheckNamePresent,MUST +0,1,INSERT,CheckPropPresent,SHOULD +0,1,INSERT,SetImpToRecByDefault,MUST + +0,1,UPDATE,CheckNamePresent,MUST +0,1,UPDATE,CheckPropPresent,SHOULD +0,1,UPDATE,SetImpToRecByDefault,MUST + +## record rules +0,2,INSERT,CheckNamePresent,SHOULD +0,2,INSERT,CheckPropPresent,SHOULD +0,2,INSERT,CheckParPresent,MUST +0,2,INSERT,SetImpToFix,MUST + +0,2,UPDATE,CheckNamePresent,SHOULD +0,2,UPDATE,CheckPropPresent,SHOULD +0,2,UPDATE,CheckParPresent,MUST +0,2,UPDATE,SetImpToFix,MUST + +## file rules +0,3,INSERT,CheckNamePresent,SHOULD +0,3,INSERT,MatchFileProp,MUST +0,3,INSERT,CheckTargetPathValid,MUST +0,3,INSERT,SetImpToFix,MUST + +0,3,UPDATE,CheckNamePresent,SHOULD +0,3,UPDATE,MatchFileProp,MUST +0,3,UPDATE,CheckTargetPathValid,MUST +0,3,UPDATE,SetImpToFix,MUST + +## property rules +0,4,INSERT,CheckDatatypePresent,MUST +0,4,UPDATE,CheckDatatypePresent,MUST +0,4,INSERT,CheckNamePresent,MUST +0,4,UPDATE,CheckNamePresent,MUST +0,4,INSERT,SetImpToFix,MUST +0,4,UPDATE,SetImpToFix,MUST + +## query template rules +0,8,UPDATE,CheckQueryTemplate,MUST +0,8,INSERT,CheckQueryTemplate,MUST + +# data type specific rules +## reference rules +0,11,INSERT,CheckRefidPresent,SHOULD +0,11,INSERT,CheckRefidValid,MUST +0,11,INSERT,CheckRefidIsaParRefid,SHOULD + +0,11,UPDATE,CheckRefidPresent,SHOULD +0,11,UPDATE,CheckRefidValid,MUST +0,11,UPDATE,CheckRefidIsaParRefid,SHOULD + +## integer rules +0,12,INSERT,CheckUnitPresent,SHOULD +0,12,INSERT,ParseUnit,SHOULD + +0,12,UPDATE,CheckUnitPresent,SHOULD +0,12,UPDATE,ParseUnit,SHOULD + +## double rules +0,13,INSERT,CheckUnitPresent,SHOULD +0,13,INSERT,ParseUnit,SHOULD + +0,13,UPDATE,CheckUnitPresent,SHOULD +0,13,UPDATE,ParseUnit,SHOULD + +## filereference rules +0,17,INSERT,CheckRefidValid,MUST +0,17,INSERT,CheckRefidIsaParRefid,MUST + +0,17,UPDATE,CheckRefidValid,MUST +0,17,UPDATE,CheckRefidIsaParRefid,MUST diff --git a/conf/core/server.conf b/conf/core/server.conf index 76ed6030523f24f977f41a510c703fe075f30042..2e91664904c06178008a89009fdd1e73eaa5c82a 100644 --- a/conf/core/server.conf +++ b/conf/core/server.conf @@ -124,6 +124,9 @@ ONE_TIME_TOKEN_EXPIRES_MS=604800000 # Path to config file for one time tokens, see authtoken.example.yml. AUTHTOKEN_CONFIG= +# Path to config file for the job rules (e.g. which job is loaded for which kind of entity) +JOB_RULES_CONFIG=conf/core/jobs.csv + # Timeout after which a one-time token expires once it has been first consumed, # regardless of the maximum of replays that are allowed for that token. This is # only a default value. The actual timeout of tokens can be configured @@ -190,9 +193,5 @@ GLOBAL_ENTITY_PERMISSIONS_FILE=./conf/core/global_entity_permissions.xml ENTITY_VERSIONING_ENABLED=true -# -------------------------------------------------- -# Extension settings -# -------------------------------------------------- - # Enabling the state machine extension # EXT_STATE_ENTITY=ENABLE diff --git a/src/main/java/org/caosdb/server/ServerProperties.java b/src/main/java/org/caosdb/server/ServerProperties.java index 3172a3e88e7790580af1b7865c024fa2b40c017b..176492d691cc63c422e5996bb055763f8d05a751 100644 --- a/src/main/java/org/caosdb/server/ServerProperties.java +++ b/src/main/java/org/caosdb/server/ServerProperties.java @@ -133,6 +133,7 @@ public class ServerProperties extends Properties { public static final String KEY_WEBUI_HTTP_HEADER_CACHE_MAX_AGE = "WEBUI_HTTP_HEADER_CACHE_MAX_AGE"; public static final String KEY_AUTHTOKEN_CONFIG = "AUTHTOKEN_CONFIG"; + public static final String KEY_JOB_RULES_CONFIG = "JOB_RULES_CONFIG"; /** * Read the config files and initialize the server properties. @@ -150,11 +151,11 @@ public class ServerProperties extends Properties { loadConfigFile(serverProperties, new File(basepath + "/conf/ext/server.conf")); // load ext/server.conf.d/ (ordering is determined by system collation) - File confDir = new File(basepath + "/conf/ext/server.conf.d"); + final File confDir = new File(basepath + "/conf/ext/server.conf.d"); if (confDir.exists() && confDir.isDirectory()) { - String[] confFiles = confDir.list(); + final String[] confFiles = confDir.list(); Arrays.sort(confFiles, Comparator.naturalOrder()); - for (String confFile : confFiles) { + for (final String confFile : confFiles) { // prevent backup files from being read if (confFile.endsWith(".conf")) { loadConfigFile(serverProperties, new File(confDir, confFile)); @@ -163,7 +164,7 @@ public class ServerProperties extends Properties { } // load env vars - for (java.util.Map.Entry<String, String> envvar : System.getenv().entrySet()) { + for (final java.util.Map.Entry<String, String> envvar : System.getenv().entrySet()) { if (envvar.getKey().startsWith("CAOSDB_CONFIG_") && envvar.getKey().length() > 14) { serverProperties.setProperty(envvar.getKey().substring(14), envvar.getValue()); } @@ -171,11 +172,11 @@ public class ServerProperties extends Properties { // log configuration alphabetically if (logger.isInfoEnabled()) { - ArrayList<String> names = new ArrayList<>(serverProperties.stringPropertyNames()); + final ArrayList<String> names = new ArrayList<>(serverProperties.stringPropertyNames()); Collections.sort(names); - for (String name : names) { - String val = - (name.contains("PASSW") || name.contains("SECRET")) + for (final String name : names) { + final String val = + name.contains("PASSW") || name.contains("SECRET") ? "****" : serverProperties.getProperty(name); logger.info(name + "=" + val); @@ -184,7 +185,7 @@ public class ServerProperties extends Properties { return serverProperties; } - private static void loadConfigFile(Properties serverProperties, File confFile) + private static void loadConfigFile(final Properties serverProperties, final File confFile) throws IOException { if (confFile.exists() && confFile.isFile()) { logger.info("Reading configuration from " + confFile.getAbsolutePath()); diff --git a/src/main/java/org/caosdb/server/database/BackendTransaction.java b/src/main/java/org/caosdb/server/database/BackendTransaction.java index 965e7181d1d010528b341f6cbf34d4c0c67a9e5c..8accff648bd97da90d7d4138ea4463ea6b5c98be 100644 --- a/src/main/java/org/caosdb/server/database/BackendTransaction.java +++ b/src/main/java/org/caosdb/server/database/BackendTransaction.java @@ -61,7 +61,6 @@ import org.caosdb.server.database.backend.implementation.MySQL.MySQLRetrieveRole import org.caosdb.server.database.backend.implementation.MySQL.MySQLRetrieveSparseEntity; import org.caosdb.server.database.backend.implementation.MySQL.MySQLRetrieveUser; import org.caosdb.server.database.backend.implementation.MySQL.MySQLRetrieveVersionHistory; -import org.caosdb.server.database.backend.implementation.MySQL.MySQLRuleLoader; import org.caosdb.server.database.backend.implementation.MySQL.MySQLSetFileCheckedTimestampImpl; import org.caosdb.server.database.backend.implementation.MySQL.MySQLSetFileChecksum; import org.caosdb.server.database.backend.implementation.MySQL.MySQLSetPassword; @@ -117,7 +116,6 @@ import org.caosdb.server.database.backend.interfaces.RetrieveRoleImpl; import org.caosdb.server.database.backend.interfaces.RetrieveSparseEntityImpl; import org.caosdb.server.database.backend.interfaces.RetrieveUserImpl; import org.caosdb.server.database.backend.interfaces.RetrieveVersionHistoryImpl; -import org.caosdb.server.database.backend.interfaces.RuleLoaderImpl; import org.caosdb.server.database.backend.interfaces.SetFileCheckedTimestampImpl; import org.caosdb.server.database.backend.interfaces.SetFileChecksumImpl; import org.caosdb.server.database.backend.interfaces.SetPasswordImpl; @@ -129,8 +127,6 @@ import org.caosdb.server.database.backend.interfaces.UpdateUserImpl; import org.caosdb.server.database.backend.interfaces.UpdateUserRolesImpl; import org.caosdb.server.database.exceptions.TransactionException; import org.caosdb.server.database.misc.TransactionBenchmark; -import org.caosdb.server.entity.EntityInterface; -import org.caosdb.server.entity.RetrieveEntity; import org.caosdb.server.utils.UndoHandler; import org.caosdb.server.utils.Undoable; @@ -183,7 +179,6 @@ public abstract class BackendTransaction implements Undoable { setImpl(GetFileRecordByPathImpl.class, MySQLGetFileRecordByPath.class); setImpl(RetrievePropertiesImpl.class, MySQLRetrieveProperties.class); setImpl(RetrieveSparseEntityImpl.class, MySQLRetrieveSparseEntity.class); - setImpl(RuleLoaderImpl.class, MySQLRuleLoader.class); setImpl(SyncStatsImpl.class, MySQLSyncStats.class); setImpl(FileExists.class, UnixFileSystemFileExists.class); setImpl(FileWasModifiedAfter.class, UnixFileSystemFileWasModifiedAfter.class); @@ -251,7 +246,7 @@ public abstract class BackendTransaction implements Undoable { protected <T extends BackendTransactionImpl> T getImplementation(final Class<T> clz) { init(); try { - Class<?> implclz = impl.get(clz); + final Class<?> implclz = impl.get(clz); final T ret = (T) implclz.getConstructor(Access.class).newInstance(this.access); if (ret instanceof Undoable) { this.undoHandler.append((Undoable) ret); @@ -289,18 +284,13 @@ public abstract class BackendTransaction implements Undoable { } /** Set the benchmark object for this AbstractTransaction. */ - public void setTransactionBenchmark(TransactionBenchmark b) { + public void setTransactionBenchmark(final TransactionBenchmark b) { this.benchmark = b; } - public void addMeasurement(Object o, long time) { + public void addMeasurement(final Object o, final long time) { if (this.benchmark != null) { this.benchmark.addMeasurement(o, time); } } - - /** Return the type of transaction of an entity, e.g. "Retrieve" for a {@link RetrieveEntity}. */ - public String getTransactionType(EntityInterface e) { - return e.getClass().getSimpleName().replace("Entity", ""); - } } diff --git a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRuleLoader.java b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRuleLoader.java index 525c3973e5267d336727e723a383aff575dd8493..8b34fe2aee9a28a3fdeb56c7c7cbc03e53233982 100644 --- a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRuleLoader.java +++ b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRuleLoader.java @@ -1,80 +1,81 @@ -/* - * ** header v3.0 - * This file is a part of the CaosDB Project. - * - * Copyright (C) 2018 Research Group Biomedical Physics, - * Max-Planck-Institute for Dynamics and Self-Organization Göttingen - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <https://www.gnu.org/licenses/>. - * - * ** end header - */ -package org.caosdb.server.database.backend.implementation.MySQL; - -import static org.caosdb.server.database.DatabaseUtils.bytes2UTF8; - -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.util.ArrayList; -import org.caosdb.server.database.access.Access; -import org.caosdb.server.database.backend.interfaces.RuleLoaderImpl; -import org.caosdb.server.database.exceptions.TransactionException; -import org.caosdb.server.database.proto.Rule; -import org.caosdb.server.jobs.core.Mode; - -public class MySQLRuleLoader extends MySQLTransaction implements RuleLoaderImpl { - - public MySQLRuleLoader(final Access access) { - super(access); - } - - public static final String STMT_GET_RULES = - "SELECT rules.transaction, rules.criterion, rules.modus from rules where rules.domain_id=? AND rules.entity_id=? AND rules.transaction=?"; - - @Override - public ArrayList<Rule> executeNoCache( - final Integer domain, final Integer entity, final String transaction) - throws TransactionException { - try { - final PreparedStatement stmt = prepareStatement(STMT_GET_RULES); - - stmt.setInt(1, domain); - stmt.setInt(2, entity); - stmt.setString(3, transaction); - stmt.execute(); - - final ResultSet rs = stmt.executeQuery(); - try { - final ArrayList<Rule> ret = new ArrayList<Rule>(); - while (rs.next()) { - final Rule r = new Rule(); - r.mode = Mode.valueOf(bytes2UTF8(rs.getBytes("modus"))); - r.job = bytes2UTF8(rs.getBytes("criterion")); - r.domain = domain; - r.entity = entity; - r.transaction = transaction; - ret.add(r); - } - return ret; - } finally { - rs.close(); - } - } catch (final SQLException e) { - throw new TransactionException(e); - } catch (final ConnectionException e) { - throw new TransactionException(e); - } - } -} +/// * +// * ** header v3.0 +// * This file is a part of the CaosDB Project. +// * +// * Copyright (C) 2018 Research Group Biomedical Physics, +// * Max-Planck-Institute for Dynamics and Self-Organization Göttingen +// * +// * This program is free software: you can redistribute it and/or modify +// * it under the terms of the GNU Affero General Public License as +// * published by the Free Software Foundation, either version 3 of the +// * License, or (at your option) any later version. +// * +// * This program is distributed in the hope that it will be useful, +// * but WITHOUT ANY WARRANTY; without even the implied warranty of +// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// * GNU Affero General Public License for more details. +// * +// * You should have received a copy of the GNU Affero General Public License +// * along with this program. If not, see <https://www.gnu.org/licenses/>. +// * +// * ** end header +// */ +// package org.caosdb.server.database.backend.implementation.MySQL; +// +// import static org.caosdb.server.database.DatabaseUtils.bytes2UTF8; +// +// import java.sql.PreparedStatement; +// import java.sql.ResultSet; +// import java.sql.SQLException; +// import java.util.ArrayList; +// import org.caosdb.server.database.access.Access; +// import org.caosdb.server.database.backend.interfaces.RuleLoaderImpl; +// import org.caosdb.server.database.exceptions.TransactionException; +// import org.caosdb.server.database.proto.Rule; +// import org.caosdb.server.jobs.core.Mode; +// +// public class MySQLRuleLoader extends MySQLTransaction implements RuleLoaderImpl { +// +// public MySQLRuleLoader(final Access access) { +// super(access); +// } +// +// public static final String STMT_GET_RULES = +// "SELECT rules.transaction, rules.criterion, rules.modus from rules where rules.domain_id=? +// AND rules.entity_id=? AND rules.transaction=?"; +// +// @Override +// public ArrayList<Rule> executeNoCache( +// final Integer domain, final Integer entity, final String transaction) +// throws TransactionException { +// try { +// final PreparedStatement stmt = prepareStatement(STMT_GET_RULES); +// +// stmt.setInt(1, domain); +// stmt.setInt(2, entity); +// stmt.setString(3, transaction); +// stmt.execute(); +// +// final ResultSet rs = stmt.executeQuery(); +// try { +// final ArrayList<Rule> ret = new ArrayList<Rule>(); +// while (rs.next()) { +// final Rule r = new Rule(); +// r.mode = Mode.valueOf(bytes2UTF8(rs.getBytes("modus"))); +// r.job = bytes2UTF8(rs.getBytes("criterion")); +// r.domain = domain; +// r.entity = entity; +// r.transaction = transaction; +// ret.add(r); +// } +// 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/interfaces/RuleLoaderImpl.java b/src/main/java/org/caosdb/server/database/backend/interfaces/RuleLoaderImpl.java index 151e3d9d21d02d953cffeec8a55b4e01377fc023..66fd1abd9286d34964fc0f00096a17eba8dd56e3 100644 --- a/src/main/java/org/caosdb/server/database/backend/interfaces/RuleLoaderImpl.java +++ b/src/main/java/org/caosdb/server/database/backend/interfaces/RuleLoaderImpl.java @@ -1,33 +1,33 @@ -/* - * ** header v3.0 - * This file is a part of the CaosDB Project. - * - * Copyright (C) 2018 Research Group Biomedical Physics, - * Max-Planck-Institute for Dynamics and Self-Organization Göttingen - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <https://www.gnu.org/licenses/>. - * - * ** end header - */ -package org.caosdb.server.database.backend.interfaces; - -import java.util.ArrayList; -import org.caosdb.server.database.exceptions.TransactionException; -import org.caosdb.server.database.proto.Rule; - -public interface RuleLoaderImpl extends BackendTransactionImpl { - - public ArrayList<Rule> executeNoCache(Integer domain, Integer entity, String transaction) - throws TransactionException; -} +/// * +// * ** header v3.0 +// * This file is a part of the CaosDB Project. +// * +// * Copyright (C) 2018 Research Group Biomedical Physics, +// * Max-Planck-Institute for Dynamics and Self-Organization Göttingen +// * +// * This program is free software: you can redistribute it and/or modify +// * it under the terms of the GNU Affero General Public License as +// * published by the Free Software Foundation, either version 3 of the +// * License, or (at your option) any later version. +// * +// * This program is distributed in the hope that it will be useful, +// * but WITHOUT ANY WARRANTY; without even the implied warranty of +// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// * GNU Affero General Public License for more details. +// * +// * You should have received a copy of the GNU Affero General Public License +// * along with this program. If not, see <https://www.gnu.org/licenses/>. +// * +// * ** end header +// */ +// package org.caosdb.server.database.backend.interfaces; +// +// import java.util.ArrayList; +// import org.caosdb.server.database.exceptions.TransactionException; +// import org.caosdb.server.database.proto.Rule; +// +// public interface RuleLoaderImpl extends BackendTransactionImpl { +// +// public ArrayList<Rule> executeNoCache(Integer domain, Integer entity, String transaction) +// throws TransactionException; +// } diff --git a/src/main/java/org/caosdb/server/database/backend/transaction/InsertTransactionHistory.java b/src/main/java/org/caosdb/server/database/backend/transaction/InsertTransactionHistory.java index 9b798de4279de8034168c4ecfbc1738c70370a65..eb345f85ebdd4372feed664c9db12944870b1078 100644 --- a/src/main/java/org/caosdb/server/database/backend/transaction/InsertTransactionHistory.java +++ b/src/main/java/org/caosdb/server/database/backend/transaction/InsertTransactionHistory.java @@ -34,7 +34,9 @@ import org.caosdb.server.utils.EntityStatus; * Record the current transaction in the entities transaction history. * * @author Timm Fitschen (t.fitschen@indiscale.com) + * @deprecated To be replaced by versioning history. */ +@Deprecated public class InsertTransactionHistory extends BackendTransaction { private final TransactionContainer container; @@ -62,7 +64,7 @@ public class InsertTransactionHistory extends BackendTransaction { || e.getEntityStatus() == EntityStatus.VALID) { t.execute( - getTransactionType(e), + e.getClass().getSimpleName().replace("Entity", ""), this.realm, this.user, this.datetime.getUTCSeconds(), diff --git a/src/main/java/org/caosdb/server/database/backend/transaction/RuleLoader.java b/src/main/java/org/caosdb/server/database/backend/transaction/RuleLoader.java index c73023bd4429db19ca0a968aa99b657f0d0cbea4..753265f0fb6007de47c0b03e0b8bb5f6a7f47a5c 100644 --- a/src/main/java/org/caosdb/server/database/backend/transaction/RuleLoader.java +++ b/src/main/java/org/caosdb/server/database/backend/transaction/RuleLoader.java @@ -1,96 +1,96 @@ -/* - * ** header v3.0 - * This file is a part of the CaosDB Project. - * - * Copyright (C) 2018 Research Group Biomedical Physics, - * Max-Planck-Institute for Dynamics and Self-Organization Göttingen - * Copyright (C) 2019 IndiScale GmbH - * Copyright (C) 2019 Timm Fitschen (t.fitschen@indiscale.com) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <https://www.gnu.org/licenses/>. - * - * ** end header - */ -package org.caosdb.server.database.backend.transaction; - -import java.util.ArrayList; -import org.apache.commons.jcs.access.behavior.ICacheAccess; -import org.caosdb.server.caching.Cache; -import org.caosdb.server.database.CacheableBackendTransaction; -import org.caosdb.server.database.backend.interfaces.RuleLoaderImpl; -import org.caosdb.server.database.exceptions.TransactionException; -import org.caosdb.server.database.proto.Rule; -import org.caosdb.server.entity.EntityInterface; -import org.caosdb.server.entity.container.TransactionContainer; -import org.caosdb.server.entity.wrapper.Property; -import org.caosdb.server.jobs.Job; -import org.caosdb.server.transaction.Transaction; - -/** - * Load transaction rules (e.g. "Referenced entities must always exist") and their configuration - * from the back-end. - * - * @author Timm Fitschen (t.fitschen@indiscale.com) - */ -public class RuleLoader extends CacheableBackendTransaction<String, ArrayList<Rule>> { - - private static final ICacheAccess<String, ArrayList<Rule>> cache = - Cache.getCache("BACKEND_JobRules"); - private final Transaction<? extends TransactionContainer> transaction; - private final EntityInterface e; - private final Integer entity; - private final Integer domain; - private ArrayList<Job> jobs; - private String transactionType; - - public RuleLoader( - final Integer domain, - final Integer entity, - final EntityInterface e, - final Transaction<? extends TransactionContainer> transaction) { - super(cache); - this.domain = domain; - this.entity = entity; - this.e = e; - if (e instanceof Property) { - this.transactionType = getTransactionType(((Property) e).getDomainEntity()); - } else { - this.transactionType = getTransactionType(e); - } - this.transaction = transaction; - } - - @Override - protected String getKey() { - return "<" + this.domain + "," + this.entity + "," + this.transactionType + ">"; - } - - public ArrayList<Job> getJobs() { - return this.jobs; - } - - @Override - public ArrayList<Rule> executeNoCache() throws TransactionException { - final RuleLoaderImpl t = getImplementation(RuleLoaderImpl.class); - return t.executeNoCache(this.domain, this.entity, this.transactionType); - } - - @Override - protected void process(final ArrayList<Rule> t) throws TransactionException { - this.jobs = new ArrayList<Job>(); - for (final Rule r : t) { - this.jobs.add(Job.getJob(r.job, r.mode, this.e, this.transaction)); - } - } -} +/// * +// * ** header v3.0 +// * This file is a part of the CaosDB Project. +// * +// * Copyright (C) 2018 Research Group Biomedical Physics, +// * Max-Planck-Institute for Dynamics and Self-Organization Göttingen +// * Copyright (C) 2019 IndiScale GmbH +// * Copyright (C) 2019 Timm Fitschen (t.fitschen@indiscale.com) +// * +// * This program is free software: you can redistribute it and/or modify +// * it under the terms of the GNU Affero General Public License as +// * published by the Free Software Foundation, either version 3 of the +// * License, or (at your option) any later version. +// * +// * This program is distributed in the hope that it will be useful, +// * but WITHOUT ANY WARRANTY; without even the implied warranty of +// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// * GNU Affero General Public License for more details. +// * +// * You should have received a copy of the GNU Affero General Public License +// * along with this program. If not, see <https://www.gnu.org/licenses/>. +// * +// * ** end header +// */ +// package org.caosdb.server.database.backend.transaction; +// +// import java.util.ArrayList; +// import org.apache.commons.jcs.access.behavior.ICacheAccess; +// import org.caosdb.server.caching.Cache; +// import org.caosdb.server.database.CacheableBackendTransaction; +// import org.caosdb.server.database.backend.interfaces.RuleLoaderImpl; +// import org.caosdb.server.database.exceptions.TransactionException; +// import org.caosdb.server.database.proto.Rule; +// import org.caosdb.server.entity.EntityInterface; +// import org.caosdb.server.entity.container.TransactionContainer; +// import org.caosdb.server.entity.wrapper.Property; +// import org.caosdb.server.jobs.Job; +// import org.caosdb.server.transaction.Transaction; +// +/// ** +// * Load transaction rules (e.g. "Referenced entities must always exist") and their configuration +// * from the back-end. +// * +// * @author Timm Fitschen (t.fitschen@indiscale.com) +// */ +// public class RuleLoader extends CacheableBackendTransaction<String, ArrayList<Rule>> { +// +// private static final ICacheAccess<String, ArrayList<Rule>> cache = +// Cache.getCache("BACKEND_JobRules"); +// private final Transaction<? extends TransactionContainer> transaction; +// private final EntityInterface e; +// private final Integer entity; +// private final Integer domain; +// private ArrayList<Job> jobs; +// private String transactionType; +// +// public RuleLoader( +// final Integer domain, +// final Integer entity, +// final EntityInterface e, +// final Transaction<? extends TransactionContainer> transaction) { +// super(cache); +// this.domain = domain; +// this.entity = entity; +// this.e = e; +// if (e instanceof Property) { +// this.transactionType = getTransactionType(((Property) e).getDomainEntity()); +// } else { +// this.transactionType = getTransactionType(e); +// } +// this.transaction = transaction; +// } +// +// @Override +// protected String getKey() { +// return "<" + this.domain + "," + this.entity + "," + this.transactionType + ">"; +// } +// +// public ArrayList<Job> getJobs() { +// return this.jobs; +// } +// +// @Override +// public ArrayList<Rule> executeNoCache() throws TransactionException { +// final RuleLoaderImpl t = getImplementation(RuleLoaderImpl.class); +// return t.executeNoCache(this.domain, this.entity, this.transactionType); +// } +// +// @Override +// protected void process(final ArrayList<Rule> t) throws TransactionException { +// this.jobs = new ArrayList<Job>(); +// for (final Rule r : t) { +// this.jobs.add(Job.getJob(r.job, r.mode, this.e, this.transaction)); +// } +// } +// } diff --git a/src/main/java/org/caosdb/server/jobs/Job.java b/src/main/java/org/caosdb/server/jobs/Job.java index 9432fc3916606cddbf64922850d7f2f9ff5284c2..0f0875edbe9e183b10909b61296da9c8cae9f920 100644 --- a/src/main/java/org/caosdb/server/jobs/Job.java +++ b/src/main/java/org/caosdb/server/jobs/Job.java @@ -40,14 +40,16 @@ import org.caosdb.server.database.backend.transaction.IsSubType; import org.caosdb.server.database.backend.transaction.RetrieveFullEntityTransaction; import org.caosdb.server.database.backend.transaction.RetrieveParents; import org.caosdb.server.database.backend.transaction.RetrieveSparseEntity; -import org.caosdb.server.database.backend.transaction.RuleLoader; +import org.caosdb.server.database.exceptions.EntityWasNotUniqueException; import org.caosdb.server.database.exceptions.TransactionException; import org.caosdb.server.datatype.AbstractCollectionDatatype; import org.caosdb.server.datatype.AbstractDatatype; import org.caosdb.server.datatype.ReferenceDatatype2; import org.caosdb.server.entity.EntityInterface; import org.caosdb.server.entity.Message; +import org.caosdb.server.entity.container.ParentContainer; import org.caosdb.server.entity.container.TransactionContainer; +import org.caosdb.server.entity.wrapper.Parent; import org.caosdb.server.jobs.core.Mode; import org.caosdb.server.transaction.Transaction; import org.caosdb.server.utils.EntityStatus; @@ -63,6 +65,8 @@ public abstract class Job { /** All known Job classes, by name (actually lowercase getSimpleName()). */ static HashMap<String, Class<? extends Job>> allClasses = null; + private final JobConfig jobConfig = JobConfig.getInstance(); + private static List<Class<? extends Job>> loadAlways; private Transaction<? extends TransactionContainer> transaction = null; @@ -149,10 +153,68 @@ public abstract class Job { return this.mode; } + protected final void setMode(final Mode mode) { + this.mode = mode; + } + TransactionContainer getContainer() { return getTransaction().getContainer(); } + /** + * This assumes that the parent has either an id (persistent or temporary) or a name. + * + * @param child + * @param parent + * @return + * @throws Message + */ + protected final boolean isSubType(final EntityInterface child, final EntityInterface parent) + throws EntityWasNotUniqueException { + if (parent.hasId()) { + if (parent.getId().equals(child.getId())) { + return true; + } + + } else if (parent.hasName()) { + if (parent.getName().equals(child.getName())) { + return true; + } + } + + // check direct parents of child + ParentContainer directParents; + if (child.hasParents()) { + directParents = child.getParents(); + } else { + directParents = resolve(child).getParents(); + } + for (final Parent directParent : directParents) { + EntityInterface resolvedDirectParent = null; + if (directParent.hasId()) { + resolvedDirectParent = getEntityById(directParent.getId()); + } else if (directParent.hasName()) { + resolvedDirectParent = getEntityByName(directParent.getName()); + } + + if (resolvedDirectParent != null) { + if (isSubType(resolvedDirectParent, parent)) { + return true; + } + } else if (directParent.hasId()) { + if (isValidSubType(directParent.getId(), parent.getId())) { + return true; + } + } else if (directParent.hasName()) { + if (isValidSubType(resolve(directParent).getId(), parent.getId())) { + return true; + } + } + } + + return false; + } + protected final boolean isValidSubType(final int child, final int parent) { if (!"false".equals(getContainer().getFlags().get("cache"))) { final HashMap<String, Boolean> isSubTypeCache = getCache("isSubType"); @@ -187,17 +249,17 @@ public abstract class Job { // the targeted entity version is the entity after the transaction or the // entity without a specific version. Thus we have to fetch the entity // from the container if possible. - EntityInterface ret = getEntityById(id); + final EntityInterface ret = getEntityById(id); if (ret != null) { return ret; } } else if (version.startsWith("HEAD~")) { - EntityInterface entById = getEntityById(id); + final EntityInterface entById = getEntityById(id); if (entById != null && entById.getEntityStatus() != EntityStatus.VALID) { // if version is HEAD~{OFFSET} with {OFFSET} > 0 and the targeted entity is is to be // updated, the actual offset has to be reduced by 1. HEAD always denotes the entity@HEAD // *after* the successful transaction, so that it is consistent with subsequent retrieves. - int offset = Integer.parseInt(version.substring(5)) - 1; + final int offset = Integer.parseInt(version.substring(5)) - 1; if (offset == 0) { // special case HEAD~1 resulting_version = "HEAD"; @@ -215,7 +277,7 @@ public abstract class Job { return ret; } - protected final EntityInterface retrieveValidEntity(Integer id) { + protected final EntityInterface retrieveValidEntity(final Integer id) { return execute(new RetrieveFullEntityTransaction(id)).getContainer().get(0); } @@ -316,14 +378,12 @@ public abstract class Job { return new ArrayList<Job>(); } if (dt instanceof ReferenceDatatype2) { - final RuleLoader t = new RuleLoader(0, 17, entity, transaction); - return execute(t).getJobs(); + return jobConfig.getConfiguredJobs(0, 17, entity, transaction); } else if (dt instanceof AbstractCollectionDatatype) { final AbstractDatatype datatype = ((AbstractCollectionDatatype) dt).getDatatype(); return loadDataTypeSpecificJobs(datatype, entity, transaction); } else if (dt.getId() != null) { - final RuleLoader t = new RuleLoader(0, dt.getId(), entity, transaction); - return execute(t).getJobs(); + return jobConfig.getConfiguredJobs(0, dt.getId(), entity, transaction); } else { return null; } @@ -334,7 +394,7 @@ public abstract class Job { final ArrayList<Job> jobs = new ArrayList<>(); // load permanent jobs - for (Class<? extends Job> j : loadAlways) { + for (final Class<? extends Job> j : loadAlways) { if (EntityJob.class.isAssignableFrom(j) && j.getAnnotation(JobAnnotation.class).transaction().isInstance(transaction)) { jobs.add(getJob(j, Mode.MUST, entity, transaction)); @@ -342,28 +402,34 @@ public abstract class Job { } // load general rules - { - final RuleLoader t = new RuleLoader(0, 0, entity, transaction); - jobs.addAll(execute(t).getJobs()); + final List<Job> generalRules = jobConfig.getConfiguredJobs(0, 0, entity, transaction); + if (generalRules != null) { + jobs.addAll(generalRules); } // load Role specific rules if (entity.hasRole()) { - final RuleLoader t = new RuleLoader(0, entity.getRole().getId(), entity, transaction); - jobs.addAll(execute(t).getJobs()); + final List<Job> roleRules = + jobConfig.getConfiguredJobs(0, entity.getRole().getId(), entity, transaction); + if (roleRules != null) { + jobs.addAll(roleRules); + } } // load data type specific rules - jobs.addAll(loadDataTypeSpecificJobs(entity, transaction)); + final List<Job> datatypeRules = loadDataTypeSpecificJobs(entity, transaction); + if (datatypeRules != null) { + jobs.addAll(datatypeRules); + } return jobs; } private static Job getJob( - Class<? extends Job> jobClass, - Mode mode, - EntityInterface entity, - Transaction<? extends TransactionContainer> transaction) { + final Class<? extends Job> jobClass, + final Mode mode, + final EntityInterface entity, + final Transaction<? extends TransactionContainer> transaction) { Job ret; try { @@ -472,15 +538,38 @@ public abstract class Job { return this.stage; } + protected EntityInterface resolve(final EntityInterface entity) + throws EntityWasNotUniqueException { + EntityInterface resolvedParent = null; + // if the parent has an id>0, it is to be a valid entity. + if (!entity.hasId() && entity.hasName()) { + resolvedParent = getEntityByName(entity.getName()); + if (resolvedParent == null) { + final Integer eid = retrieveValidIDByName(entity.getName()); + entity.setId(eid); + } + } + + if (entity.hasId()) { + // get parent from container + resolvedParent = getEntityById(entity.getId()); + if (resolvedParent == null) { + resolvedParent = retrieveValidEntity(entity.getId()); + } + } + + return resolvedParent; + } + /** * Return those matching jobs which are annotated with the "loadAlways" attribute. * * @return A list with the jobs. */ - public static List<Job> loadPermanentContainerJobs(Transaction<?> transaction) { + public static List<Job> loadPermanentContainerJobs(final Transaction<?> transaction) { final ArrayList<Job> jobs = new ArrayList<>(); // load permanent jobs: ContainerJob classes with the correct transaction - for (Class<? extends Job> j : loadAlways) { + for (final Class<? extends Job> j : loadAlways) { if (ContainerJob.class.isAssignableFrom(j) && j.getAnnotation(JobAnnotation.class).transaction().isInstance(transaction)) { jobs.add(getJob(j, Mode.MUST, null, transaction)); diff --git a/src/main/java/org/caosdb/server/jobs/JobConfig.java b/src/main/java/org/caosdb/server/jobs/JobConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..6cceeae3bffb545cbfa82af7540fe2e9c602ba6c --- /dev/null +++ b/src/main/java/org/caosdb/server/jobs/JobConfig.java @@ -0,0 +1,134 @@ +package org.caosdb.server.jobs; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import org.caosdb.server.CaosDBServer; +import org.caosdb.server.ServerProperties; +import org.caosdb.server.entity.EntityInterface; +import org.caosdb.server.entity.RetrieveEntity; +import org.caosdb.server.entity.container.TransactionContainer; +import org.caosdb.server.entity.wrapper.Property; +import org.caosdb.server.jobs.core.Mode; +import org.caosdb.server.transaction.Transaction; +import org.caosdb.server.utils.ConfigurationException; + +public class JobConfig { + + private static JobConfig instance; + + static { + try { + instance = new JobConfig(); + } catch (final Exception e) { + e.printStackTrace(); + // CaosDB Server cannot work without a proper job configuration. + System.exit(1); + } + } + + private final Map<String, List<Object[]>> rules; + + public JobConfig() throws FileNotFoundException, IOException { + this(new File(CaosDBServer.getServerProperty(ServerProperties.KEY_JOB_RULES_CONFIG))); + } + + public JobConfig(final File config) throws FileNotFoundException, IOException { + this.rules = getRulesFromCSV(config); + } + + private Map<String, List<Object[]>> getRulesFromCSV(final File file) + throws FileNotFoundException, IOException { + try (final BufferedReader reader = new BufferedReader(new FileReader(file))) { + final Map<String, List<Object[]>> result = new HashMap<>(); + reader + .lines() + .forEach( + line -> { + // cut away comments, trim, then split into columns + final String[] row = line.replaceFirst("#.*$", "").strip().split("\\s*,\\s*"); + if (row.length == 1 && row[0].isBlank()) { + // comment or empty line + // ignore + return; + } + addRule(result, row); + }); + return result; + } + } + + private String getKey(final Integer domain, final Integer entity, final String transaction) { + final StringBuilder sb = new StringBuilder(); + sb.append("<"); + sb.append(domain); + sb.append(","); + sb.append(transaction.toLowerCase()); + sb.append(","); + sb.append(entity); + sb.append(">"); + return sb.toString(); + } + + private void addRule(final Map<String, List<Object[]>> result, final String[] row) { + if (row.length != 5) { + throw new ConfigurationException( + "Could not parse the job rules. Lines of four comma-separated values expected"); + } + try { + final Integer domain = Integer.parseInt(row[0]); + final Integer entity = Integer.parseInt(row[1]); + final String transaction = row[2]; + final String job = row[3]; + final Mode mode = Mode.valueOf(row[4]); + final String key = getKey(domain, entity, transaction); + if (!result.containsKey(key)) { + result.put(key, new ArrayList<>()); + } + result.get(key).add(new Object[] {job, mode}); + + } catch (final Exception e) { + throw new ConfigurationException("Could not parse the job rules.", e); + } + } + + public List<Job> getConfiguredJobs( + final Integer domain, + final Integer entity, + final EntityInterface target, + final Transaction<? extends TransactionContainer> transaction) { + String transactionType; + if (target instanceof Property) { + transactionType = getTransactionType(((Property) target).getDomainEntity()); + } else { + transactionType = getTransactionType(target); + } + final String key = getKey(domain, entity, transactionType); + final List<Object[]> jobRules = rules.get(key); + if (jobRules != null) { + final List<Job> result = new LinkedList<>(); + jobRules.forEach( + config -> { + result.add(Job.getJob((String) config[0], (Mode) config[1], target, transaction)); + }); + return result; + } + return null; + } + + public static JobConfig getInstance() { + return instance; + } + + /** Return the type of transaction of an entity, e.g. "Retrieve" for a {@link RetrieveEntity}. */ + String getTransactionType(final EntityInterface e) { + return e.getClass().getSimpleName().replace("Entity", ""); + } +} diff --git a/src/main/java/org/caosdb/server/jobs/core/CheckDatatypePresent.java b/src/main/java/org/caosdb/server/jobs/core/CheckDatatypePresent.java index f46013d278f268b95b1b105130e1eabd74f3a9f4..f9c7704c8bd0ff00214b41d5793c8fdb15bcc61a 100644 --- a/src/main/java/org/caosdb/server/jobs/core/CheckDatatypePresent.java +++ b/src/main/java/org/caosdb/server/jobs/core/CheckDatatypePresent.java @@ -82,9 +82,12 @@ public final class CheckDatatypePresent extends EntityJob { // run jobsreturn this.entities; final List<Job> datatypeJobs = loadDataTypeSpecificJobs(); - List<ScheduledJob> scheduledJobs = getTransaction().getSchedule().addAll(datatypeJobs); - for (final ScheduledJob job : scheduledJobs) { - getTransaction().getSchedule().runJob(job); + if (datatypeJobs != null) { + final List<ScheduledJob> scheduledJobs = + getTransaction().getSchedule().addAll(datatypeJobs); + for (final ScheduledJob job : scheduledJobs) { + getTransaction().getSchedule().runJob(job); + } } } else { diff --git a/src/main/java/org/caosdb/server/jobs/core/CheckParOblPropPresent.java b/src/main/java/org/caosdb/server/jobs/core/CheckParOblPropPresent.java index 7aca2bac3dd3709006497f9ce8f556f267c4ca81..fc557f8907c22799dc1400a17c5377fb677306e5 100644 --- a/src/main/java/org/caosdb/server/jobs/core/CheckParOblPropPresent.java +++ b/src/main/java/org/caosdb/server/jobs/core/CheckParOblPropPresent.java @@ -22,9 +22,11 @@ */ package org.caosdb.server.jobs.core; -import org.caosdb.server.database.backend.transaction.RetrieveFullEntityTransaction; import org.caosdb.server.entity.EntityInterface; +import org.caosdb.server.entity.Message; import org.caosdb.server.entity.StatementStatus; +import org.caosdb.server.entity.wrapper.Parent; +import org.caosdb.server.entity.wrapper.Property; import org.caosdb.server.jobs.EntityJob; import org.caosdb.server.utils.EntityStatus; import org.caosdb.server.utils.ServerMessages; @@ -35,93 +37,114 @@ import org.caosdb.server.utils.ServerMessages; * @author tf */ public class CheckParOblPropPresent extends EntityJob { + + public static final Message ENTITY_NOT_UNIQUE = + new Message("Error", 0, "Could not check importance. Parent was not uniquely resolvable."); + + public static final String OBL_IMPORTANCE_FLAG_KEY = "force-missing-obligatory"; + @Override public final void run() { + if (getEntity().getEntityStatus() != EntityStatus.QUALIFIED + || getEntity() instanceof Property + || getEntity() instanceof Parent) { + return; + } + if (getEntity().hasParents()) { - // This test needs the valid/qualified ids of all properties. - // Thus, run a CheckPropValid first. - runJobFromSchedule(getEntity(), CheckPropValid.class); + handleImportanceFlags(); + if (getMode() == Mode.IGNORE) { + // importance is to be ignored + return; + } - // This test needs the valid/qualified parents. If a parent is only - // referenced by name it has to be translated into a (hopefully) unique - // id. - runJobFromSchedule(getEntity(), CheckParValid.class); + // inherit properties + runJobFromSchedule(getEntity(), Inheritance.class); - if (getEntity().hasParents()) { - execute(new RetrieveFullEntityTransaction(getEntity().getParents())); + // get importance-flags + + // loop over all parents + for (final EntityInterface parent : getEntity().getParents()) { + final EntityInterface resolvedParent = resolve(parent); + checkObligatoryPropertiesMissing(resolvedParent); + } } + } - // inherit properties - runJobFromSchedule(getEntity(), Inheritance.class); + /** + * Check if all obligatory properties of the given parent are present. Add a warning, + * + * @param parent + * @throws Message + */ + private void checkObligatoryPropertiesMissing(final EntityInterface parent) { + // does this parent have properties? + if (parent.hasProperties()) { - // loop over all parents - if (getEntity().hasParents()) { - for (final EntityInterface parent : getEntity().getParents()) { + // loop over all properties of this parent + outerLoop: + for (final EntityInterface parentProperty : parent.getProperties()) { - // if the parent has an id>0, it is to be a valid entity. - if (parent.hasId() && parent.getId() > 0) { - if (parent.getEntityStatus() == EntityStatus.NONEXISTENT) { - continue; - } + // only obligatory properties are of interest. + if (parentProperty.hasStatementStatus() + && parentProperty.getStatementStatus() == StatementStatus.OBLIGATORY) { - // does this parent have properties? - if (parent.hasProperties()) { - - // loop over all properties of this parent - outerLoop: - for (final EntityInterface parentProperty : parent.getProperties()) { - - // only obligatory properties are of interest. - if (parentProperty.hasStatementStatus() - && parentProperty.getStatementStatus() == StatementStatus.OBLIGATORY) { - - // loop over all properties of the entity - innerLoop: - for (final EntityInterface entityProperty : getEntity().getProperties()) { - - if (!entityProperty.hasId()) { - // continue inner loop means that this - // is not the entityProperty in - // question, continue search for an - // entityProperty. - continue innerLoop; - } - - // TODO sub type instead of equals!!! - if (parentProperty.getId().equals(entityProperty.getId())) { - // continue outer loop means that we - // found an entityProperty that - // implements the obligatory - // parentProperty, continue with the - // other parentProperties. - continue outerLoop; - } - } - - // No entityProperty has been found which - // implements this parentProperty. Add the - // respective messages. - switch (getMode()) { - case MUST: - // TODO add information WHICH property is - // missing. - getEntity().addError(ServerMessages.OBLIGATORY_PROPERTY_MISSING); - break; - case SHOULD: - // TODO add information WHICH property is - // missing. - getEntity().addWarning(ServerMessages.OBLIGATORY_PROPERTY_MISSING); - break; - default: - break; - } - } + // loop over all properties of the entity + for (final EntityInterface entityProperty : getEntity().getProperties()) { + + if (isSubType(entityProperty, parentProperty)) { + // continue outer loop means that we + // found an entityProperty that + // implements the obligatory + // parentProperty, continue with the + // other parentProperties. + continue outerLoop; } } - } else { - // TODO add processing of new parents, new properties + + // No entityProperty has been found which + // implements this parentProperty. Add the + // respective messages. + switch (getMode()) { + case MUST: + // TODO add information WHICH property is + // missing. + getEntity().addError(ServerMessages.OBLIGATORY_PROPERTY_MISSING); + break; + case SHOULD: + // TODO add information WHICH property is + // missing. + getEntity().addWarning(ServerMessages.OBLIGATORY_PROPERTY_MISSING); + break; + default: + // should never be reached, but ignoring is the way to go anyway + break; + } } } } } + + private void handleImportanceFlags() { + final String globalFlag = + getTransaction().getContainer().getFlags().get(OBL_IMPORTANCE_FLAG_KEY); + final String entityFlag = + getEntity().getFlags().getOrDefault(OBL_IMPORTANCE_FLAG_KEY, globalFlag); + if (entityFlag != null) { + switch (entityFlag) { + case "error": + setMode(Mode.MUST); + break; + case "warn": + setMode(Mode.SHOULD); + break; + case "ignore": + setMode(Mode.IGNORE); + break; + default: + // do nothing + break; + } + } + } } diff --git a/src/main/java/org/caosdb/server/jobs/core/InheritInitialState.java b/src/main/java/org/caosdb/server/jobs/core/InheritInitialState.java index 39b66e35330a485bbd8eb4c40724ab3b250ccc45..89ea4dcc17d143720c55a971e9eb8fcafbbfb30c 100644 --- a/src/main/java/org/caosdb/server/jobs/core/InheritInitialState.java +++ b/src/main/java/org/caosdb/server/jobs/core/InheritInitialState.java @@ -1,32 +1,37 @@ package org.caosdb.server.jobs.core; import java.util.List; +import org.caosdb.server.entity.EntityInterface; import org.caosdb.server.entity.Message; -import org.caosdb.server.entity.wrapper.Parent; import org.caosdb.server.entity.wrapper.Property; import org.caosdb.server.jobs.JobAnnotation; import org.caosdb.server.jobs.TransactionStage; +import org.caosdb.server.utils.EntityStatus; @JobAnnotation(stage = TransactionStage.CHECK) public class InheritInitialState extends EntityStateJob { @Override protected void run() { + if (getEntity().getEntityStatus() != EntityStatus.QUALIFIED) { + return; + } try { - State parentState = getFirstParentState(); + final State parentState = getFirstParentState(); if (parentState != null) { getEntity().addMessage(parentState); parentState.getStateEntity(); getEntity().setEntityACL(parentState.getStateACL()); } - } catch (Message e) { + } catch (final Message e) { getEntity().addError(e); } } private State getFirstParentState() throws Message { - for (Parent par : getEntity().getParents()) { - List<Property> stateProperties = getStateProperties(par, false); + for (EntityInterface par : getEntity().getParents()) { + par = resolve(par); + final List<Property> stateProperties = getStateProperties(par, false); if (stateProperties.size() > 0) { return createState(stateProperties.get(0)); } diff --git a/src/main/java/org/caosdb/server/jobs/core/Inheritance.java b/src/main/java/org/caosdb/server/jobs/core/Inheritance.java index 107c5768a5c15fbb05ea6d68b0cb956cbb235687..8b26ba196ce8d4d32f03acda8b38b6524217fcc1 100644 --- a/src/main/java/org/caosdb/server/jobs/core/Inheritance.java +++ b/src/main/java/org/caosdb/server/jobs/core/Inheritance.java @@ -65,7 +65,7 @@ public class Inheritance extends EntityJob { @Override protected void run() { if (getEntity() instanceof InsertEntity || getEntity() instanceof UpdateEntity) { - if (getEntity().hasParents()) { + if (getEntity().getEntityStatus() == EntityStatus.QUALIFIED && getEntity().hasParents()) { final ArrayList<Property> transfer = new ArrayList<>(); parentLoop: for (final EntityInterface parent : getEntity().getParents()) { @@ -108,7 +108,7 @@ public class Inheritance extends EntityJob { } } // prop's Datatype might need to be resolved. - ScheduledJob job = this.appendJob(prop, CheckDatatypePresent.class); + final ScheduledJob job = this.appendJob(prop, CheckDatatypePresent.class); getTransaction().getSchedule().runJob(job); getEntity().addProperty(new Property(prop.getWrapped())); @@ -133,12 +133,13 @@ public class Inheritance extends EntityJob { break propertyLoop; } - runJobFromSchedule(getEntity(), CheckPropValid.class); - EntityInterface validProperty = new Entity(property.getId()); if (getEntity().hasParents()) { outer: - for (final EntityInterface par : getEntity().getParents()) { + for (EntityInterface par : getEntity().getParents()) { + if (!par.hasProperties()) { + par = resolve(par); + } for (final EntityInterface prop : par.getProperties()) { if (validProperty.hasId() && validProperty.getId().equals(prop.getId())) { validProperty = prop; @@ -166,7 +167,7 @@ public class Inheritance extends EntityJob { } } // prop's Datatype might need to be resolved. - ScheduledJob job = this.appendJob(prop, CheckDatatypePresent.class); + final ScheduledJob job = this.appendJob(prop, CheckDatatypePresent.class); getTransaction().getSchedule().runJob(job); property.addProperty(new Property(prop.getWrapped())); @@ -187,7 +188,9 @@ public class Inheritance extends EntityJob { * @param inheritance */ private void collectInheritedProperties( - List<Property> transfer, EntityInterface from, INHERITANCE_MODE inheritance) { + final List<Property> transfer, + final EntityInterface from, + final INHERITANCE_MODE inheritance) { if (from.hasProperties()) { for (final Property propProperty : from.getProperties()) { switch (inheritance) { diff --git a/src/main/java/org/caosdb/server/jobs/core/Mode.java b/src/main/java/org/caosdb/server/jobs/core/Mode.java index 561a4b0c5fcb4e2c8c66a2b9f9a02ea91c44c119..e786c7fe5eb2f7d1e7da3f4ea1f831d37b3f4755 100644 --- a/src/main/java/org/caosdb/server/jobs/core/Mode.java +++ b/src/main/java/org/caosdb/server/jobs/core/Mode.java @@ -25,6 +25,5 @@ package org.caosdb.server.jobs.core; public enum Mode { MUST, SHOULD, - SHOULDNT, - MUSTNOT + IGNORE, } diff --git a/src/main/java/org/caosdb/server/jobs/core/UniqueName.java b/src/main/java/org/caosdb/server/jobs/core/UniqueName.java index 300e32bf1662e4990f52bda0570a65c05e5d60b8..2836ccf742ebbd1bbda5a8ed3ae95ac0d69c2fbb 100644 --- a/src/main/java/org/caosdb/server/jobs/core/UniqueName.java +++ b/src/main/java/org/caosdb/server/jobs/core/UniqueName.java @@ -27,10 +27,11 @@ import org.caosdb.server.database.exceptions.EntityWasNotUniqueException; import org.caosdb.server.entity.EntityInterface; import org.caosdb.server.jobs.FlagJob; import org.caosdb.server.jobs.JobAnnotation; +import org.caosdb.server.jobs.TransactionStage; import org.caosdb.server.utils.EntityStatus; import org.caosdb.server.utils.ServerMessages; -@JobAnnotation(flag = "uniquename") +@JobAnnotation(flag = "uniquename", stage = TransactionStage.PRE_CHECK) public class UniqueName extends FlagJob { private void doCheck(final EntityInterface entity) { diff --git a/src/main/java/org/caosdb/server/utils/ConfigurationException.java b/src/main/java/org/caosdb/server/utils/ConfigurationException.java index 517c11677c73d9624b93df18fb5196e2f4578024..df04ac169f3c8113443a69795840a50774bef0b1 100644 --- a/src/main/java/org/caosdb/server/utils/ConfigurationException.java +++ b/src/main/java/org/caosdb/server/utils/ConfigurationException.java @@ -26,7 +26,11 @@ public class ConfigurationException extends RuntimeException { private static final long serialVersionUID = -8973694831724941007L; - public ConfigurationException(String reason) { + public ConfigurationException(final String reason) { super(reason); } + + public ConfigurationException(final String reason, final Throwable cause) { + super(reason, cause); + } } diff --git a/src/test/java/org/caosdb/server/database/backend/transaction/BackendTransactionTest.java b/src/test/java/org/caosdb/server/database/backend/transaction/BackendTransactionTest.java deleted file mode 100644 index 93402bf0d06c487451d9219b4487f301eb855f49..0000000000000000000000000000000000000000 --- a/src/test/java/org/caosdb/server/database/backend/transaction/BackendTransactionTest.java +++ /dev/null @@ -1,24 +0,0 @@ -package org.caosdb.server.database.backend.transaction; - -import static org.junit.Assert.assertEquals; - -import org.caosdb.server.database.BackendTransaction; -import org.caosdb.server.entity.DeleteEntity; -import org.caosdb.server.entity.InsertEntity; -import org.caosdb.server.entity.RetrieveEntity; -import org.caosdb.server.entity.Role; -import org.caosdb.server.entity.UpdateEntity; -import org.jdom2.Element; -import org.junit.Test; - -public class BackendTransactionTest { - - @Test - public void testGetTransactionType() { - BackendTransaction transaction = new InsertTransactionHistory(null, null, null, null); - assertEquals("Retrieve", transaction.getTransactionType(new RetrieveEntity("test"))); - assertEquals("Insert", transaction.getTransactionType(new InsertEntity("test", Role.Record))); - assertEquals("Delete", transaction.getTransactionType(new DeleteEntity(1234))); - assertEquals("Update", transaction.getTransactionType(new UpdateEntity(new Element("Record")))); - } -} diff --git a/src/test/java/org/caosdb/server/jobs/JobConfigTest.java b/src/test/java/org/caosdb/server/jobs/JobConfigTest.java new file mode 100644 index 0000000000000000000000000000000000000000..bdaa63f5bf0b781e19fc5b7ea8388816f425812f --- /dev/null +++ b/src/test/java/org/caosdb/server/jobs/JobConfigTest.java @@ -0,0 +1,23 @@ +package org.caosdb.server.jobs; + +import static org.junit.Assert.assertEquals; + +import org.caosdb.server.entity.DeleteEntity; +import org.caosdb.server.entity.InsertEntity; +import org.caosdb.server.entity.RetrieveEntity; +import org.caosdb.server.entity.Role; +import org.caosdb.server.entity.UpdateEntity; +import org.jdom2.Element; +import org.junit.Test; + +public class JobConfigTest { + + @Test + public void testGetTransactionType() { + final JobConfig jobConfig = JobConfig.getInstance(); + assertEquals("Retrieve", jobConfig.getTransactionType(new RetrieveEntity("test"))); + assertEquals("Insert", jobConfig.getTransactionType(new InsertEntity("test", Role.Record))); + assertEquals("Delete", jobConfig.getTransactionType(new DeleteEntity(1234))); + assertEquals("Update", jobConfig.getTransactionType(new UpdateEntity(new Element("Record")))); + } +}