diff --git a/CHANGELOG.md b/CHANGELOG.md index df5b85f849385868b6eb8e1d7daa605bc1b4e8d8..b8362aff3682a130e28f40286fcb6ce2f0f703cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +* Related to #146, a new flag for entities and complete transactions: + `force-missing-obligatory=[ignore|warn|error]`. The flag overrides the + default behavior of the server (throwing an error when an obligatory property + is missing). `ignore` just discards the consistency check, `warn` only issues + a warning when obligatory properties are missing and `error` throws an error + in that case. The flag can be set for the complete transaction and each + single entity, while the entity flag takes precedence. * New EntityState plug-in. The plug-in disabled by default and can be enabled by setting the server property `EXT_ENTITY_STATE=ENABLED`. See [!62](https://gitlab.com/caosdb/caosdb-server/-/merge_requests/62) for more @@ -31,6 +38,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +* #146 - Default behavior for missing obligatory properties * #131 - CQL Parsing error when white space characters before some units. * #134 - CQL Parsing error when multiple white space characters after `FROM`. * #130 - Error during `FIND ENTITY` when diff --git a/conf/core/jobs.csv b/conf/core/jobs.csv new file mode 100644 index 0000000000000000000000000000000000000000..84795bee8657288473188cd9e7a6af81f6295221 --- /dev/null +++ b/conf/core/jobs.csv @@ -0,0 +1,110 @@ +# +# This file is a part of the CaosDB Project. +# +# Copyright (C) 2021 Timm Fitsche <t.fitschen@indiscale.com> +# Copyright (C) 2021 IndiScale GmbH <info@indiscale.com> +# +# 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, JOB_FAILURE_SEVERITY + +# general rules +0,0,INSERT,CheckPropValid,ERROR +0,0,INSERT,CheckParValid,ERROR +0,0,INSERT,CheckParOblPropPresent,ERROR +0,0,INSERT,CheckValueParsable,ERROR +0,0,UPDATE,CheckPropValid,ERROR +0,0,UPDATE,CheckParValid,ERROR +0,0,UPDATE,CheckParOblPropPresent,ERROR +0,0,UPDATE,CheckValueParsable,ERROR +0,0,DELETE,CheckReferenceDependencyExistent,ERROR +0,0,DELETE,CheckChildDependencyExistent,ERROR + + +# role specific rules +## recordtype rules +0,1,INSERT,CheckNamePresent,ERROR +0,1,INSERT,CheckPropPresent,WARN +0,1,INSERT,SetImpToRecByDefault,ERROR + +0,1,UPDATE,CheckNamePresent,ERROR +0,1,UPDATE,CheckPropPresent,WARN +0,1,UPDATE,SetImpToRecByDefault,ERROR + +## record rules +0,2,INSERT,CheckNamePresent,WARN +0,2,INSERT,CheckPropPresent,WARN +0,2,INSERT,CheckParPresent,ERROR +0,2,INSERT,SetImpToFix,ERROR + +0,2,UPDATE,CheckNamePresent,WARN +0,2,UPDATE,CheckPropPresent,WARN +0,2,UPDATE,CheckParPresent,ERROR +0,2,UPDATE,SetImpToFix,ERROR + +## file rules +0,3,INSERT,CheckNamePresent,WARN +0,3,INSERT,MatchFileProp,ERROR +0,3,INSERT,CheckTargetPathValid,ERROR +0,3,INSERT,SetImpToFix,ERROR + +0,3,UPDATE,CheckNamePresent,WARN +0,3,UPDATE,MatchFileProp,ERROR +0,3,UPDATE,CheckTargetPathValid,ERROR +0,3,UPDATE,SetImpToFix,ERROR + +## property rules +0,4,INSERT,CheckDatatypePresent,ERROR +0,4,UPDATE,CheckDatatypePresent,ERROR +0,4,INSERT,CheckNamePresent,ERROR +0,4,UPDATE,CheckNamePresent,ERROR +0,4,INSERT,SetImpToFix,ERROR +0,4,UPDATE,SetImpToFix,ERROR + +## query template rules +0,8,UPDATE,CheckQueryTemplate,ERROR +0,8,INSERT,CheckQueryTemplate,ERROR + +# data type specific rules +## reference rules +0,11,INSERT,CheckRefidPresent,WARN +0,11,INSERT,CheckRefidValid,ERROR +0,11,INSERT,CheckRefidIsaParRefid,WARN + +0,11,UPDATE,CheckRefidPresent,WARN +0,11,UPDATE,CheckRefidValid,ERROR +0,11,UPDATE,CheckRefidIsaParRefid,WARN + +## integer rules +0,12,INSERT,CheckUnitPresent,WARN +0,12,INSERT,ParseUnit,WARN + +0,12,UPDATE,CheckUnitPresent,WARN +0,12,UPDATE,ParseUnit,WARN + +## double rules +0,13,INSERT,CheckUnitPresent,WARN +0,13,INSERT,ParseUnit,WARN + +0,13,UPDATE,CheckUnitPresent,WARN +0,13,UPDATE,ParseUnit,WARN + +## filereference rules +0,17,INSERT,CheckRefidValid,ERROR +0,17,INSERT,CheckRefidIsaParRefid,ERROR + +0,17,UPDATE,CheckRefidValid,ERROR +0,17,UPDATE,CheckRefidIsaParRefid,ERROR 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 deleted file mode 100644 index 525c3973e5267d336727e723a383aff575dd8493..0000000000000000000000000000000000000000 --- a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRuleLoader.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * ** header v3.0 - * This file is a part of the CaosDB Project. - * - * Copyright (C) 2018 Research Group Biomedical Physics, - * Max-Planck-Institute for Dynamics and Self-Organization Göttingen - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <https://www.gnu.org/licenses/>. - * - * ** end header - */ -package org.caosdb.server.database.backend.implementation.MySQL; - -import 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 deleted file mode 100644 index 151e3d9d21d02d953cffeec8a55b4e01377fc023..0000000000000000000000000000000000000000 --- a/src/main/java/org/caosdb/server/database/backend/interfaces/RuleLoaderImpl.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * ** header v3.0 - * This file is a part of the CaosDB Project. - * - * Copyright (C) 2018 Research Group Biomedical Physics, - * Max-Planck-Institute for Dynamics and Self-Organization Göttingen - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <https://www.gnu.org/licenses/>. - * - * ** end header - */ -package org.caosdb.server.database.backend.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 deleted file mode 100644 index c73023bd4429db19ca0a968aa99b657f0d0cbea4..0000000000000000000000000000000000000000 --- a/src/main/java/org/caosdb/server/database/backend/transaction/RuleLoader.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * ** header v3.0 - * This file is a part of the CaosDB Project. - * - * Copyright (C) 2018 Research Group Biomedical Physics, - * Max-Planck-Institute for Dynamics and Self-Organization Göttingen - * 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/database/proto/Rule.java b/src/main/java/org/caosdb/server/database/proto/Rule.java deleted file mode 100644 index 3d25f7662052b5b59f45255218c1191cf011761e..0000000000000000000000000000000000000000 --- a/src/main/java/org/caosdb/server/database/proto/Rule.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * ** header v3.0 - * This file is a part of the CaosDB Project. - * - * Copyright (C) 2018 Research Group Biomedical Physics, - * Max-Planck-Institute for Dynamics and Self-Organization Göttingen - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <https://www.gnu.org/licenses/>. - * - * ** end header - */ -package org.caosdb.server.database.proto; - -import java.io.Serializable; -import org.caosdb.server.jobs.core.Mode; - -public class Rule implements Serializable { - - private static final long serialVersionUID = 1122097540596265945L; - - public int domain = 0; - public int entity = 0; - public String job = null; - public String transaction = null; - public Mode mode = null; -} diff --git a/src/main/java/org/caosdb/server/jobs/Job.java b/src/main/java/org/caosdb/server/jobs/Job.java index 9432fc3916606cddbf64922850d7f2f9ff5284c2..cc943fbd0baf211d7f467cb54a7797616218f608 100644 --- a/src/main/java/org/caosdb/server/jobs/Job.java +++ b/src/main/java/org/caosdb/server/jobs/Job.java @@ -40,15 +40,17 @@ 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.jobs.core.Mode; +import org.caosdb.server.entity.wrapper.Parent; +import org.caosdb.server.jobs.core.JobFailureSeverity; import org.caosdb.server.transaction.Transaction; import org.caosdb.server.utils.EntityStatus; import org.caosdb.server.utils.ServerMessages; @@ -63,10 +65,12 @@ 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; - private Mode mode = null; + private JobFailureSeverity failureSeverity = null; private final TransactionStage stage; private EntityInterface entity = null; @@ -95,7 +99,7 @@ public abstract class Job { protected ScheduledJob appendJob(final EntityInterface entity, final Class<? extends Job> clazz) { try { final Job job = clazz.getDeclaredConstructor().newInstance(); - job.init(getMode(), entity, getTransaction()); + job.init(getFailureSeverity(), entity, getTransaction()); return getTransaction().getSchedule().add(job); } catch (InstantiationException | IllegalAccessException @@ -124,10 +128,10 @@ public abstract class Job { } public final Job init( - final Mode mode, + final JobFailureSeverity severity, final EntityInterface entity, final Transaction<? extends TransactionContainer> transaction) { - this.mode = mode; + this.failureSeverity = severity; this.entity = entity; this.transaction = transaction; return this; @@ -145,14 +149,85 @@ public abstract class Job { return this.entity; } - protected final Mode getMode() { - return this.mode; + protected final JobFailureSeverity getFailureSeverity() { + return this.failureSeverity; + } + + protected final void setFailureSeverity(final JobFailureSeverity severiy) { + this.failureSeverity = severiy; } TransactionContainer getContainer() { return getTransaction().getContainer(); } + /** + * Check if an entity ('child') is a direct or indirect child of another entity ('targetParent'). + * + * <p>This assumes that the parent has either an id (persistent or temporary) or a name. + * + * <p>If the targetParent is not in the set of parents of the child, this method iterates through + * the direct parents of the child. + * + * <p>If both entities are part of this transaction, they are resolved within this transaction. + * Otherwise they are fetched from the database backend or the whole evaluation takes place in the + * backend (if both have persistent ids and are not part of this transaction). + * + * <p>If both entities have the same id or name, the return value is true without any further + * checks. + * + * @param child the child entity + * @param targetParent the parent entity + * @return true iff targetParent is a direct or indirect parent of the child or when the ids or + * names match. + * @throws Message + */ + protected final boolean isSubType(final EntityInterface child, final EntityInterface targetParent) + throws EntityWasNotUniqueException { + if (targetParent.hasId()) { + if (targetParent.getId().equals(child.getId())) { + return true; + } + + } else if (targetParent.hasName()) { + if (targetParent.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, targetParent)) { + return true; + } + } else if (directParent.hasId()) { + if (isValidSubType(directParent.getId(), targetParent.getId())) { + return true; + } + } else if (directParent.hasName()) { + if (isValidSubType(resolve(directParent).getId(), targetParent.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 +262,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 +290,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); } @@ -245,7 +320,7 @@ public abstract class Job { */ public static Job getJob( final String job, - final Mode mode, + final JobFailureSeverity severiy, final EntityInterface entity, final Transaction<? extends TransactionContainer> transaction) { // Fill `allClasses` with available subclasses @@ -253,7 +328,7 @@ public abstract class Job { // Get matching class for Job and generate it. final Class<? extends Job> jobClass = allClasses.get(job.toLowerCase()); - return getJob(jobClass, mode, entity, transaction); + return getJob(jobClass, severiy, entity, transaction); } /** @@ -316,14 +391,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,42 +407,48 @@ 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)); + jobs.add(getJob(j, JobFailureSeverity.ERROR, entity, transaction)); } } // 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 JobFailureSeverity severity, + final EntityInterface entity, + final Transaction<? extends TransactionContainer> transaction) { Job ret; try { if (jobClass != null) { ret = jobClass.getDeclaredConstructor().newInstance(); - ret.init(mode, entity, transaction); + ret.init(severity, entity, transaction); return ret; } return null; @@ -393,7 +472,7 @@ public abstract class Job { // load flag jobs if (!entity.getFlags().isEmpty()) { for (final String key : entity.getFlags().keySet()) { - final Job j = getJob(key, Mode.MUST, entity, transaction); + final Job j = getJob(key, JobFailureSeverity.ERROR, entity, transaction); if (j != null) { if (j instanceof FlagJob) { ((FlagJob) j).setValue(entity.getFlag(key)); @@ -408,7 +487,7 @@ public abstract class Job { for (final EntityInterface p : entity.getParents()) { if (!p.getFlags().isEmpty()) { for (final String key : p.getFlags().keySet()) { - final Job j = getJob(key, Mode.MUST, entity, transaction); + final Job j = getJob(key, JobFailureSeverity.ERROR, entity, transaction); if (j != null) { if (j instanceof FlagJob) { ((FlagJob) j).setValue(p.getFlag(key)); @@ -427,7 +506,7 @@ public abstract class Job { for (final EntityInterface p : entity.getProperties()) { if (!p.getFlags().isEmpty()) { for (final String key : p.getFlags().keySet()) { - final Job j = getJob(key, Mode.MUST, entity, transaction); + final Job j = getJob(key, JobFailureSeverity.ERROR, entity, transaction); if (j != null) { if (j instanceof FlagJob) { ((FlagJob) j).setValue(p.getFlag(key)); @@ -460,7 +539,7 @@ public abstract class Job { + "-" + (getEntity() != null ? getEntity().toString() : "NOENTITY") + " #" - + getMode().toString() + + getFailureSeverity().toString() + "]"; } @@ -472,18 +551,48 @@ public abstract class Job { return this.stage; } + /** + * Resolve an entity (which might only be specified by it's name or id) to its full representation + * (with properties, parents and all). + * + * @param entity the entity to be resolved. + * @return the resolved entity. + * @throws EntityWasNotUniqueException if the resolution failed due to ambuiguity of the name. + */ + protected EntityInterface resolve(final EntityInterface entity) + throws EntityWasNotUniqueException { + EntityInterface resolvedEntity = null; + if (!entity.hasId() && entity.hasName()) { + resolvedEntity = getEntityByName(entity.getName()); + if (resolvedEntity == null) { + final Integer eid = retrieveValidIDByName(entity.getName()); + entity.setId(eid); + } + } + + if (entity.hasId()) { + // get entity from container + resolvedEntity = getEntityById(entity.getId()); + if (resolvedEntity == null && entity.getId() > 0) { + resolvedEntity = retrieveValidEntity(entity.getId()); + } + } + + return resolvedEntity; + } + /** * 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)); + jobs.add(getJob(j, JobFailureSeverity.ERROR, null, transaction)); } } return jobs; 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..732a985a78b5f67c88d1c01dff1565e887aab13c --- /dev/null +++ b/src/main/java/org/caosdb/server/jobs/JobConfig.java @@ -0,0 +1,203 @@ +/* + * This file is a part of the CaosDB Project. + * + * Copyright (C) 2021 IndiScale GmbH <info@indiscale.com> + * Copyright (C) 2021 Timm Fitschen <t.fitschen@indiscale.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +package org.caosdb.server.jobs; + +import 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.JobFailureSeverity; +import org.caosdb.server.transaction.Transaction; +import org.caosdb.server.utils.ConfigurationException; + +/** + * Configuration of the jobs which have to run during all entity transactions with "job rules". + * + * <p>A job rule contains of a quintuple (domain, entity, transaction type, job name, job failure + * severity). + * + * <p>The domain and entity define for which entities the job (of the given name) should be loaded. + * The transaction type (e.g. Insert, Update, Delete) defines the transaction where this rule + * applies. The job failure severity sets the related property of a job. + * + * <p>The configuration is being read by default from a config file {@link + * ServerProperties#KEY_JOB_RULES_CONFIG} + * + * <p>This class works as a singleton, most of the time. It is possible to instanciate it otherwise, + * though. + * + * @author Timm Fitschen <t.fitschen@indiscale.com> + */ +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); + } + } + + /** + * The keys of these rules consist of the domain, entity and transaction. The value is a pair (job + * name, job failure severity). + */ + 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); + } + + /** + * Read the rules from the CSV file. + * + * @param file + * @return A map with all the rules. + * @throws FileNotFoundException + * @throws IOException + */ + 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; + } + } + + /** Generate a unique key (for class-internal use only). */ + 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(); + } + + /** + * Read a rule from the csv-row and put it into the 'result' map + * + * @param result map with all the rules. + * @param row represents a single rule as a comma-separated quintuple. + */ + 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 five 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 JobFailureSeverity severity = JobFailureSeverity.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, severity}); + + } catch (final Exception e) { + throw new ConfigurationException("Could not parse the job rules.", e); + } + } + + /** + * Factory method for the instanciation and configuration of jobs for a specific entity. + * + * @param domain the domain of the rule + * @param entity the entity of the rule (this might be a particular datatype or a role, like + * "RecordType") + * @param target the entity for which the job will run. + * @param transaction the transaction for which the job will run. + * @return Fresh job instances, one for each matching job rule. + */ + 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], (JobFailureSeverity) 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/CheckDescPresent.java b/src/main/java/org/caosdb/server/jobs/core/CheckDescPresent.java index fc3a5114f240c796c114f92f8e6271e4fd3bd9ac..e430a0817e9083013330bc4d59d8f7377d10d62a 100644 --- a/src/main/java/org/caosdb/server/jobs/core/CheckDescPresent.java +++ b/src/main/java/org/caosdb/server/jobs/core/CheckDescPresent.java @@ -35,12 +35,12 @@ public class CheckDescPresent extends EntityJob { @Override public final void run() { if (!getEntity().hasDescription()) { - switch (getMode()) { - case MUST: + switch (getFailureSeverity()) { + case ERROR: getEntity().addError(ServerMessages.ENTITY_HAS_NO_DESCRIPTION); getEntity().setEntityStatus(EntityStatus.UNQUALIFIED); break; - case SHOULD: + case WARN: getEntity().addWarning(ServerMessages.ENTITY_HAS_NO_DESCRIPTION); default: break; diff --git a/src/main/java/org/caosdb/server/jobs/core/CheckNamePresent.java b/src/main/java/org/caosdb/server/jobs/core/CheckNamePresent.java index f1600ce42117ab5f37fccd33b79d821efeccb774..3166057c95671273efe27357ce11b2ffe10e2c78 100644 --- a/src/main/java/org/caosdb/server/jobs/core/CheckNamePresent.java +++ b/src/main/java/org/caosdb/server/jobs/core/CheckNamePresent.java @@ -35,12 +35,12 @@ public class CheckNamePresent extends EntityJob { @Override public final void run() { if (!getEntity().hasName()) { - switch (getMode()) { - case MUST: + switch (getFailureSeverity()) { + case ERROR: getEntity().addError(ServerMessages.ENTITY_HAS_NO_NAME); getEntity().setEntityStatus(EntityStatus.UNQUALIFIED); break; - case SHOULD: + case WARN: getEntity().addWarning(ServerMessages.ENTITY_HAS_NO_NAME); break; default: diff --git a/src/main/java/org/caosdb/server/jobs/core/CheckNoAdditionalPropertiesPresent.java b/src/main/java/org/caosdb/server/jobs/core/CheckNoAdditionalPropertiesPresent.java index b04a9151ab5c142e97a6efc9b19607896ab38daf..fb6919649242a906b36526e3725fc25e710c9d60 100644 --- a/src/main/java/org/caosdb/server/jobs/core/CheckNoAdditionalPropertiesPresent.java +++ b/src/main/java/org/caosdb/server/jobs/core/CheckNoAdditionalPropertiesPresent.java @@ -45,7 +45,7 @@ public class CheckNoAdditionalPropertiesPresent extends EntityJob { } private void addMessage(EntityInterface property, Message message) { - if (getMode() == Mode.MUST) { + if (getFailureSeverity() == JobFailureSeverity.ERROR) { property.addError(message); } else { property.addWarning(message); diff --git a/src/main/java/org/caosdb/server/jobs/core/CheckNoOverridesPresent.java b/src/main/java/org/caosdb/server/jobs/core/CheckNoOverridesPresent.java index e439354e88f39675491682877dd27aa54fd7a3ef..d6cae3ee40222d3b3d6eb89e1b2d0c8bce1da0e5 100644 --- a/src/main/java/org/caosdb/server/jobs/core/CheckNoOverridesPresent.java +++ b/src/main/java/org/caosdb/server/jobs/core/CheckNoOverridesPresent.java @@ -28,7 +28,7 @@ public class CheckNoOverridesPresent extends EntityJob { } private void addMessage(Property p, Message message) { - if (getMode() == Mode.MUST) { + if (getFailureSeverity() == JobFailureSeverity.ERROR) { p.addError(message); } else { p.addWarning(message); 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..b706afdbcd2082fa2ac3d76c0128959abfb42bd9 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,12 @@ */ 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.Message.MessageType; 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 +38,110 @@ import org.caosdb.server.utils.ServerMessages; * @author tf */ public class CheckParOblPropPresent extends EntityJob { - @Override - public final void run() { - - // This test needs the valid/qualified ids of all properties. - // Thus, run a CheckPropValid first. - runJobFromSchedule(getEntity(), CheckPropValid.class); - // 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); + public static final String OBL_IMPORTANCE_FLAG_KEY = "force-missing-obligatory"; + public static final Message ENTITY_NOT_UNIQUE = + new Message( + MessageType.Error, 0, "Could not check importance. Parent was not uniquely resolvable."); + public static final Message ILLEGAL_FLAG_VALUE = + new Message(MessageType.Warning, "Illegal value for flag 'force-missing-obligatory'."); - if (getEntity().hasParents()) { - execute(new RetrieveFullEntityTransaction(getEntity().getParents())); + @Override + public final void run() { + if (getEntity().getEntityStatus() != EntityStatus.QUALIFIED + || getEntity() instanceof Property + || getEntity() instanceof Parent) { + return; } - - // inherit properties - runJobFromSchedule(getEntity(), Inheritance.class); - - // loop over all parents if (getEntity().hasParents()) { - for (final EntityInterface parent : getEntity().getParents()) { - // 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; - } + handleImportanceFlags(); + if (getFailureSeverity() == JobFailureSeverity.IGNORE) { + // importance is to be ignored + return; + } - // does this parent have properties? - if (parent.hasProperties()) { + // inherit properties + runJobFromSchedule(getEntity(), Inheritance.class); - // loop over all properties of this parent - outerLoop: - for (final EntityInterface parentProperty : parent.getProperties()) { + // get importance-flags - // only obligatory properties are of interest. - if (parentProperty.hasStatementStatus() - && parentProperty.getStatementStatus() == StatementStatus.OBLIGATORY) { + // loop over all parents + for (final EntityInterface parent : getEntity().getParents()) { + final EntityInterface resolvedParent = resolve(parent); + checkObligatoryPropertiesMissing(resolvedParent); + } + } + } + + /** + * Check if all obligatory properties of the given parent are present. + * + * <p>Add an error or warning if not. + * + * @param parent + * @throws Message + */ + private void checkObligatoryPropertiesMissing(final EntityInterface parent) { + // does this parent have properties? + if (parent.hasProperties()) { - // loop over all properties of the entity - innerLoop: - for (final EntityInterface entityProperty : getEntity().getProperties()) { + // loop over all properties of this parent + outerLoop: + for (final EntityInterface parentProperty : parent.getProperties()) { - if (!entityProperty.hasId()) { - // continue inner loop means that this - // is not the entityProperty in - // question, continue search for an - // entityProperty. - continue innerLoop; - } + // only obligatory properties are of interest. + if (parentProperty.hasStatementStatus() + && parentProperty.getStatementStatus() == StatementStatus.OBLIGATORY) { - // 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; - } - } + // loop over all properties of the entity + for (final EntityInterface entityProperty : getEntity().getProperties()) { - // 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; - } - } + 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 (getFailureSeverity()) { + case ERROR: + // TODO add information WHICH property is + // missing. + getEntity().addError(ServerMessages.OBLIGATORY_PROPERTY_MISSING); + break; + case WARN: + // 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; + } } } } } + + /** Look at the entity's importance flag first, at the container's flag second. */ + 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) { + try { + setFailureSeverity(JobFailureSeverity.valueOf(entityFlag.toUpperCase())); + } catch (final IllegalArgumentException e) { + getEntity().addWarning(ILLEGAL_FLAG_VALUE); + } + } + } } diff --git a/src/main/java/org/caosdb/server/jobs/core/CheckParPresent.java b/src/main/java/org/caosdb/server/jobs/core/CheckParPresent.java index 8851456c236c0afd9f922f187089adc5f4e35ab1..058710c6bbbf5c2bbd3f95f7ae70a243d3bfcd20 100644 --- a/src/main/java/org/caosdb/server/jobs/core/CheckParPresent.java +++ b/src/main/java/org/caosdb/server/jobs/core/CheckParPresent.java @@ -35,12 +35,12 @@ public class CheckParPresent extends EntityJob { @Override public final void run() { if (!getEntity().hasParents() || getEntity().getParents().isEmpty()) { - switch (getMode()) { - case MUST: + switch (getFailureSeverity()) { + case ERROR: getEntity().addError(ServerMessages.ENTITY_HAS_NO_PARENTS); getEntity().setEntityStatus(EntityStatus.UNQUALIFIED); break; - case SHOULD: + case WARN: getEntity().addWarning(ServerMessages.ENTITY_HAS_NO_PARENTS); default: break; diff --git a/src/main/java/org/caosdb/server/jobs/core/CheckPropPresent.java b/src/main/java/org/caosdb/server/jobs/core/CheckPropPresent.java index 31a460971bc80ccf61d775c96b4632fa38f14f66..cc23d980152c44cfdbf88500e9041044a0418034 100644 --- a/src/main/java/org/caosdb/server/jobs/core/CheckPropPresent.java +++ b/src/main/java/org/caosdb/server/jobs/core/CheckPropPresent.java @@ -35,12 +35,12 @@ public class CheckPropPresent extends EntityJob { @Override public final void run() { if (getEntity().getProperties().isEmpty()) { - switch (getMode()) { - case MUST: + switch (getFailureSeverity()) { + case ERROR: getEntity().addError(ServerMessages.ENTITY_HAS_NO_PROPERTIES); getEntity().setEntityStatus(EntityStatus.UNQUALIFIED); break; - case SHOULD: + case WARN: getEntity().addWarning(ServerMessages.ENTITY_HAS_NO_PROPERTIES); default: break; diff --git a/src/main/java/org/caosdb/server/jobs/core/CheckUnitPresent.java b/src/main/java/org/caosdb/server/jobs/core/CheckUnitPresent.java index aa87ecfff69635407bc90d372c89f089e4f308dc..5e3e6120fadef8f4b519ec694ebbf7588d7c2488 100644 --- a/src/main/java/org/caosdb/server/jobs/core/CheckUnitPresent.java +++ b/src/main/java/org/caosdb/server/jobs/core/CheckUnitPresent.java @@ -40,12 +40,12 @@ public class CheckUnitPresent extends EntityJob { runJobFromSchedule(getEntity(), Inheritance.class); if (!hasUnit(getEntity())) { - switch (getMode()) { - case MUST: + switch (getFailureSeverity()) { + case ERROR: getEntity().addError(ServerMessages.ENTITY_HAS_NO_UNIT); getEntity().setEntityStatus(EntityStatus.UNQUALIFIED); break; - case SHOULD: + case WARN: getEntity().addWarning(ServerMessages.ENTITY_HAS_NO_UNIT); default: 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/JobFailureSeverity.java similarity index 51% rename from src/main/java/org/caosdb/server/jobs/core/Mode.java rename to src/main/java/org/caosdb/server/jobs/core/JobFailureSeverity.java index 561a4b0c5fcb4e2c8c66a2b9f9a02ea91c44c119..e95bef34b7b9bea8e1b2b2126da196c1c7c53c12 100644 --- a/src/main/java/org/caosdb/server/jobs/core/Mode.java +++ b/src/main/java/org/caosdb/server/jobs/core/JobFailureSeverity.java @@ -4,6 +4,8 @@ * * Copyright (C) 2018 Research Group Biomedical Physics, * Max-Planck-Institute for Dynamics and Self-Organization Göttingen + * Copyright (C) 2021 Indiscale GmbH <info@indiscale.com> + * Copyright (C) 2021 Timm Fitschen <t.fitschen@indiscale.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -22,9 +24,22 @@ */ package org.caosdb.server.jobs.core; -public enum Mode { - MUST, - SHOULD, - SHOULDNT, - MUSTNOT +/** + * Describes the severity of a failure of a job (esp. consitency checks). + * + * <ul> + * <li>{@link #ERROR} - a failure throws an error and the transaction will not succeed. + * <li>{@link #WARN} - a failure produces a warning but the transaction may succeed anyway. + * <li>{@link #IGNORE} - the job may fail silently. Jobs can also decide to not run at all if that + * is not necessary in this case. + * <p>Jobs where this doesn't make sense at all may also ignore the severity. It is rather a + * convenient way to configure jobs where this makes sense than a definitive normative + * property of a job. + * + * @author Timm Fitschen <t.fitschen@indiscale.com> + */ +public enum JobFailureSeverity { + ERROR, + WARN, + IGNORE, } diff --git a/src/main/java/org/caosdb/server/jobs/core/LoadContainerFlagJobs.java b/src/main/java/org/caosdb/server/jobs/core/LoadContainerFlagJobs.java index ef8df214c804fb1d55c3ed088c5f7f6e998aca07..c04fd21d588c9a9a2f6e59ce7c1fff445a225c67 100644 --- a/src/main/java/org/caosdb/server/jobs/core/LoadContainerFlagJobs.java +++ b/src/main/java/org/caosdb/server/jobs/core/LoadContainerFlagJobs.java @@ -36,7 +36,7 @@ public class LoadContainerFlagJobs extends ContainerJob { protected void run() { if (getContainer().getFlags() != null) { for (final Entry<String, String> flag : getContainer().getFlags().entrySet()) { - final Job j = Job.getJob(flag.getKey(), Mode.MUST, null, getTransaction()); + final Job j = Job.getJob(flag.getKey(), JobFailureSeverity.ERROR, null, getTransaction()); if (j != null) { if (j instanceof FlagJob) { ((FlagJob) j).setValue(flag.getValue()); 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/transaction/Retrieve.java b/src/main/java/org/caosdb/server/transaction/Retrieve.java index 19f840e92a1a6812e2ae2610ed3ace23b7e3cee7..250df0423869ae97a766da2fc54131e04d5257ca 100644 --- a/src/main/java/org/caosdb/server/transaction/Retrieve.java +++ b/src/main/java/org/caosdb/server/transaction/Retrieve.java @@ -31,7 +31,7 @@ import org.caosdb.server.entity.xml.SetFieldStrategy; import org.caosdb.server.entity.xml.ToElementStrategy; import org.caosdb.server.entity.xml.ToElementable; import org.caosdb.server.jobs.ScheduledJob; -import org.caosdb.server.jobs.core.Mode; +import org.caosdb.server.jobs.core.JobFailureSeverity; import org.caosdb.server.jobs.core.RemoveDuplicates; import org.caosdb.server.jobs.core.ResolveNames; import org.caosdb.server.permissions.EntityPermission; @@ -53,14 +53,14 @@ public class Retrieve extends Transaction<RetrieveContainer> { // resolve names { final ResolveNames r = new ResolveNames(); - r.init(Mode.SHOULD, null, this); + r.init(JobFailureSeverity.WARN, null, this); ScheduledJob scheduledJob = getSchedule().add(r); getSchedule().runJob(scheduledJob); } { final RemoveDuplicates job = new RemoveDuplicates(); - job.init(Mode.MUST, null, this); + job.init(JobFailureSeverity.ERROR, null, this); ScheduledJob scheduledJob = getSchedule().add(job); getSchedule().runJob(scheduledJob); } diff --git a/src/main/java/org/caosdb/server/transaction/Transaction.java b/src/main/java/org/caosdb/server/transaction/Transaction.java index 99c305fa17b37878cc5f3277c93623a915be4ea3..b30d13b469ebe05222d2db1ab958fe0a4d3a0f26 100644 --- a/src/main/java/org/caosdb/server/transaction/Transaction.java +++ b/src/main/java/org/caosdb/server/transaction/Transaction.java @@ -45,7 +45,7 @@ import org.caosdb.server.jobs.TransactionStage; import org.caosdb.server.jobs.core.AccessControl; import org.caosdb.server.jobs.core.CheckDatatypePresent; import org.caosdb.server.jobs.core.CheckEntityACLRoles; -import org.caosdb.server.jobs.core.Mode; +import org.caosdb.server.jobs.core.JobFailureSeverity; import org.caosdb.server.jobs.core.PickUp; import org.caosdb.server.permissions.EntityACL; import org.caosdb.server.utils.AbstractObservable; @@ -96,13 +96,17 @@ public abstract class Transaction<C extends TransactionContainer> extends Abstra */ protected void makeSchedule() throws Exception { // load flag jobs - final Job loadContainerFlags = Job.getJob("LoadContainerFlagJobs", Mode.MUST, null, this); + final Job loadContainerFlags = + Job.getJob("LoadContainerFlagJobs", JobFailureSeverity.ERROR, null, this); final ScheduledJob scheduledJob = this.schedule.add(loadContainerFlags); this.schedule.runJob(scheduledJob); // AccessControl - this.schedule.add(Job.getJob(AccessControl.class.getSimpleName(), Mode.MUST, null, this)); - this.schedule.add(Job.getJob(CheckEntityACLRoles.class.getSimpleName(), Mode.MUST, null, this)); + this.schedule.add( + Job.getJob(AccessControl.class.getSimpleName(), JobFailureSeverity.ERROR, null, this)); + this.schedule.add( + Job.getJob( + CheckEntityACLRoles.class.getSimpleName(), JobFailureSeverity.ERROR, null, this)); // load permanent container jobs this.schedule.addAll(Job.loadPermanentContainerJobs(this)); @@ -113,12 +117,12 @@ public abstract class Transaction<C extends TransactionContainer> extends Abstra // additionally load datatype job if (e.hasValue()) { - this.schedule.add(new CheckDatatypePresent().init(Mode.MUST, e, this)); + this.schedule.add(new CheckDatatypePresent().init(JobFailureSeverity.ERROR, e, this)); } // load pickup job if necessary if (e.hasFileProperties() && e.getFileProperties().isPickupable()) { - this.schedule.add(new PickUp().init(Mode.MUST, e, this)); + this.schedule.add(new PickUp().init(JobFailureSeverity.ERROR, e, this)); } } } 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..70917b53553c6b94b283ed1e39b0ee81db9d466c --- /dev/null +++ b/src/test/java/org/caosdb/server/jobs/JobConfigTest.java @@ -0,0 +1,31 @@ +package org.caosdb.server.jobs; + +import static org.junit.Assert.assertEquals; + +import java.io.IOException; +import org.caosdb.server.CaosDBServer; +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.BeforeClass; +import org.junit.Test; + +public class JobConfigTest { + + @BeforeClass + public static void setup() throws IOException { + CaosDBServer.initServerProperties(); + } + + @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")))); + } +} diff --git a/src/test/java/org/caosdb/server/jobs/ScheduleTest.java b/src/test/java/org/caosdb/server/jobs/ScheduleTest.java index cef0ec3e076f38893ada35bbad881ccfe8a02331..9aa8e6f66788911f2c4314c44549d543277398d2 100644 --- a/src/test/java/org/caosdb/server/jobs/ScheduleTest.java +++ b/src/test/java/org/caosdb/server/jobs/ScheduleTest.java @@ -24,10 +24,18 @@ package org.caosdb.server.jobs; import static org.junit.Assert.fail; +import java.io.IOException; +import org.caosdb.server.CaosDBServer; +import org.junit.BeforeClass; import org.junit.Test; public class ScheduleTest { + @BeforeClass + public static void setup() throws IOException { + CaosDBServer.initServerProperties(); + } + /** The problem of ticket #297 was that a CHECK job was executed during the ROLL_BACK stage. */ @Test public void testTicket297() { diff --git a/src/test/java/org/caosdb/server/jobs/core/TestInsertFilesInDir.java b/src/test/java/org/caosdb/server/jobs/core/TestInsertFilesInDir.java index ae5df853ba5d29e681cd0fb0e5ec8eee6ec3462c..5d81ac368f65c559eb666f003f34da522f191f21 100644 --- a/src/test/java/org/caosdb/server/jobs/core/TestInsertFilesInDir.java +++ b/src/test/java/org/caosdb/server/jobs/core/TestInsertFilesInDir.java @@ -4,16 +4,23 @@ import static org.junit.Assert.assertTrue; import java.io.File; import java.io.IOException; +import org.caosdb.server.CaosDBServer; +import org.junit.BeforeClass; import org.junit.Test; public class TestInsertFilesInDir { + @BeforeClass + public static void setup() throws IOException { + CaosDBServer.initServerProperties(); + } + @Test public void testExclude() throws IOException { - InsertFilesInDir job = new InsertFilesInDir(); + final InsertFilesInDir job = new InsertFilesInDir(); job.init(null, null, null); job.parseValue("-e ^.*test.*$ test"); - File testFile = new File("test.dat"); + final File testFile = new File("test.dat"); assertTrue(job.isExcluded(testFile)); } }