From 714f1d24f283af31c0da7142a4a517168bc18eee Mon Sep 17 00:00:00 2001 From: "AD\\aabdoun" Date: Mon, 29 Dec 2025 09:30:35 +0100 Subject: [PATCH 1/8] =?UTF-8?q?Premier=20passage=20de=20l=E2=80=99analyse?= =?UTF-8?q?=20SonarQube=20sur=20le=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/storage/CoreseGraphDataManager.java | 8 +-- .../CoreseGraphDataManagerBuilder.java | 17 ----- .../corese/core/storage/DataManagerJava.java | 50 +++++--------- .../storage/api/dataManager/DataManager.java | 65 ++----------------- .../api/dataManager/DataManagerRead.java | 22 +++---- .../api/dataManager/DataManagerUpdate.java | 11 +--- 6 files changed, 32 insertions(+), 141 deletions(-) diff --git a/src/main/java/fr/inria/corese/core/storage/CoreseGraphDataManager.java b/src/main/java/fr/inria/corese/core/storage/CoreseGraphDataManager.java index 88aac3850..37190b4a6 100644 --- a/src/main/java/fr/inria/corese/core/storage/CoreseGraphDataManager.java +++ b/src/main/java/fr/inria/corese/core/storage/CoreseGraphDataManager.java @@ -42,7 +42,6 @@ protected CoreseGraphDataManager() { * Please use the CoreseGraphDataManagerBuilder to create a * CoreseGraphDataManager. * - * @param g */ protected CoreseGraphDataManager(Graph g) { setGraph(g); @@ -59,11 +58,6 @@ public void startReadTransaction() { getGraph().init(); } - @Override - public void endReadTransaction() { - - } - @Override public void startWriteTransaction() { getGraph().init(); @@ -87,7 +81,7 @@ public void start(HashMapList map) { @Override public void init(HashMapList map) { - logger.info("Init data manager: " + map); + logger.info("Init data manager: {}", map); if (map.containsKey(DEBUG)) { setDebug(map.booleanValue(DEBUG)); } diff --git a/src/main/java/fr/inria/corese/core/storage/CoreseGraphDataManagerBuilder.java b/src/main/java/fr/inria/corese/core/storage/CoreseGraphDataManagerBuilder.java index 90518f68f..a915fdf71 100644 --- a/src/main/java/fr/inria/corese/core/storage/CoreseGraphDataManagerBuilder.java +++ b/src/main/java/fr/inria/corese/core/storage/CoreseGraphDataManagerBuilder.java @@ -5,20 +5,10 @@ public class CoreseGraphDataManagerBuilder implements DataManagerBuilder { - ////////////////////////// - // Mandatory parameters // - ////////////////////////// - - ///////////////////////// - // Optional parameters // - ///////////////////////// private Graph graph; private boolean defGraph = false; - ////////////////// - // Constructors // - ////////////////// /** * Create a CoreseGraphDataManagerBuilder. @@ -26,10 +16,6 @@ public class CoreseGraphDataManagerBuilder implements DataManagerBuilder { public CoreseGraphDataManagerBuilder() { } - //////////// - // Setter // - //////////// - /** * Build the dataManager from an existing Corese Graphn * @@ -42,9 +28,6 @@ public CoreseGraphDataManagerBuilder graph(Graph graph) { return this; } - /////////// - // Build // - /////////// @Override public CoreseGraphDataManager build() { diff --git a/src/main/java/fr/inria/corese/core/storage/DataManagerJava.java b/src/main/java/fr/inria/corese/core/storage/DataManagerJava.java index f678ad643..cabdd9755 100644 --- a/src/main/java/fr/inria/corese/core/storage/DataManagerJava.java +++ b/src/main/java/fr/inria/corese/core/storage/DataManagerJava.java @@ -1,37 +1,32 @@ package fr.inria.corese.core.storage; -import java.util.ArrayList; -import java.util.List; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import fr.inria.corese.core.Graph; +import fr.inria.corese.core.kgram.api.core.Edge; +import fr.inria.corese.core.kgram.api.core.Node; +import fr.inria.corese.core.kgram.core.Mappings; import fr.inria.corese.core.load.Load; import fr.inria.corese.core.load.LoadException; import fr.inria.corese.core.load.QueryLoad; import fr.inria.corese.core.query.QueryProcess; -import fr.inria.corese.core.kgram.api.core.Edge; -import fr.inria.corese.core.kgram.api.core.Node; -import fr.inria.corese.core.kgram.core.Mappings; import fr.inria.corese.core.sparql.api.IDatatype; import fr.inria.corese.core.sparql.datatype.DatatypeMap; import fr.inria.corese.core.sparql.exceptions.EngineException; import fr.inria.corese.core.sparql.triple.function.proxy.GraphSpecificFunction; import fr.inria.corese.core.sparql.triple.parser.Access; -import fr.inria.corese.core.sparql.triple.parser.Access.Feature; -import fr.inria.corese.core.sparql.triple.parser.Access.Level; import fr.inria.corese.core.sparql.triple.parser.HashMapList; import fr.inria.corese.core.sparql.triple.parser.NSManager; import fr.inria.corese.core.sparql.triple.parser.URLServer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.List; /** * DataManager on top of json (or xml) document - * * a) implemented in java as graph data manager * path = path of insert where query that creates a graph (from json, xml, etc.) * update query creates graph data manager - * * b) implemented in ldscript when path contains ldscript (draft for testing) * path = path of ldscript function definition * 1- read (json) file with us:read() ldscript determine json file @@ -71,7 +66,7 @@ public DataManagerJava(String path) { // creation time in StorageFactory @Override public void start(HashMapList map) { - logger.info("Start data manager: " + getStoragePath()); + logger.info("Start data manager: {}", getStoragePath()); if (map == null) { init(); } else { @@ -107,7 +102,7 @@ boolean parameter(HashMapList map) { if (queryPath != null) { setQueryPath(queryPath); setQuery(null); - logger.info("Service query path= " + queryPath); + logger.info("Service query path= {}", queryPath); init(); return true; } else { @@ -116,7 +111,7 @@ boolean parameter(HashMapList map) { query = clean(query); setQuery(query); setQueryPath(null); - logger.info("Service query = " + query); + logger.info("Service query = {}", query); init(); return true; } @@ -156,14 +151,14 @@ void initgraph() { try { if (getLoad()!=null) { for (String name : getLoad()) { - logger.info("Load " + name); + logger.info("Load {}", name); ld.parse(name); } } // query who creates rdf graph (from json) String q; if (getQueryPath() != null) { - logger.info("Load " + getQueryPath()); + logger.info("Load {}", getQueryPath()); q = ql.readWE(getQueryPath()); } else if (getQuery() != null) { q = getQuery(); @@ -174,7 +169,7 @@ void initgraph() { if (getMap() != null && getMap().containsKey(PARAM)) { q = String.format(q, getMap().getFirst(PARAM)); } - logger.info("Process query:\n" + q); + logger.info("Process query:\n{}", q); // update query creates rdf graph (from json) // this is graph of current DataManager Mappings map = getQueryProcess().query(q); @@ -187,8 +182,6 @@ void initgraph() { logger.error(ex.getMessage()); } finally { - //Access.set(Feature.READ, read); - //Access.set(Feature.READ_FILE, readFile); Access.setDefaultResultWhenEmptyAccept(false); } } @@ -212,12 +205,12 @@ void initldscript() { @Override public Iterable getEdges(Node s, Node p, Node o, List graphList) { if (isLdscript()) { - return iterateJson(s, p, o, graphList); + return iterateJson(s, p, o); } return super.getEdges(s, p, o, graphList); } - Iterable iterateJson(Node s, Node p, Node o, List graphList) { + Iterable iterateJson(Node s, Node p, Node o) { try { IDatatype dt = getQueryProcess().funcall(iterateFunction, getJsonDocument(), value(s), value(p), value(o)); if (dt == null) { @@ -246,17 +239,6 @@ Iterable cast(IDatatype list) { return edgeList; } - boolean filter(IDatatype dt, Node s, Node p, Node o) { - Edge e = dt.getEdge(); - if (isJoker(p)) { - return true; - } - return e.getPropertyNode().equals(p); - } - - boolean isJoker(Node n) { - return n.getDatatypeValue().equals(joker); - } public QueryProcess getQueryProcess() { return queryProcess; diff --git a/src/main/java/fr/inria/corese/core/storage/api/dataManager/DataManager.java b/src/main/java/fr/inria/corese/core/storage/api/dataManager/DataManager.java index 6cc0a1c45..f839d5dd0 100644 --- a/src/main/java/fr/inria/corese/core/storage/api/dataManager/DataManager.java +++ b/src/main/java/fr/inria/corese/core/storage/api/dataManager/DataManager.java @@ -5,7 +5,6 @@ /** * Interface to adapt an external storage system to Corese. - * * {@code DataManagerRead} for {@code select where} SPARQL queries. * {@code DataManagerUpdate} for {@code update} and {@code construct} queries. * @@ -14,7 +13,7 @@ */ public interface DataManager extends DataManagerRead, DataManagerUpdate { - /******************* + /* ****************** * MetaDataManager * *******************/ @@ -67,35 +66,17 @@ default void init(HashMapList map) { } // manage edge index i as named graph kg:rule_i - default void setRuleDataManager(boolean b) { - } - - default boolean isRuleDataManager() { - return false; - } - - // manage integer context in edge iterator as a filter on edge index - // where edge.index >= context.intValue - default boolean isEdgeIndexContext() { - return false; + @SuppressWarnings("unused") + default void setRuleDataManager(boolean b) { } - /**************** + /* *************** * Transactions * ****************/ - /** - * Indicates whether or not this DataManage supports transactions. - * - * @return true if this DataManage supports transactions, otherwise false. - */ - default boolean transactionSupport() { - return false; - } /** * Start a read transaction. - * * A read transaction can be re-entrant, i.e. several read transactions can be * nested within each other. * Only if the transactions are supported. @@ -105,7 +86,6 @@ default void startReadTransaction() { /** * End a read transaction. - * * If several read transactions are nested within each other, this function * terminates only the most recent read transaction. * Only if the transactions are supported. @@ -115,7 +95,6 @@ default void endReadTransaction() { /** * Start a write transaction. - * * A write transaction can't be re-entrant. * Only if the transactions are supported. */ @@ -129,42 +108,6 @@ default void startWriteTransaction() { default void endWriteTransaction() { } - /** - * Abort a transaction and undo the changes. - * Only if the transactions are supported. - */ - default void abortTransaction() { - } - - /** - * Indicates whether this DataManage is in transaction or not. - * Only if the transactions are supported. - * - * @return true if this DataManage is in transaction, otherwise false. - */ - default boolean isInTransaction() { - return this.isInReadTransaction() || this.isInWriteTransaction(); - } - - /** - * Indicates whether this DataManage is in read transaction or not. - * Only if the transactions are supported. - * - * @return true if this DataManage is in read transaction, otherwise false. - */ - default boolean isInReadTransaction() { - return false; - } - - /** - * Indicates whether this DataManage is in write transaction or not. - * Only if the transactions are supported. - * - * @return true if this DataManage is in write transaction, otherwise false. - */ - default boolean isInWriteTransaction() { - return false; - } default void startRuleEngine() { } diff --git a/src/main/java/fr/inria/corese/core/storage/api/dataManager/DataManagerRead.java b/src/main/java/fr/inria/corese/core/storage/api/dataManager/DataManagerRead.java index a549ebb6c..8e3f6e4f9 100644 --- a/src/main/java/fr/inria/corese/core/storage/api/dataManager/DataManagerRead.java +++ b/src/main/java/fr/inria/corese/core/storage/api/dataManager/DataManagerRead.java @@ -1,16 +1,14 @@ package fr.inria.corese.core.storage.api.dataManager; -import java.util.ArrayList; -import java.util.List; - import fr.inria.corese.core.kgram.api.core.Edge; import fr.inria.corese.core.kgram.api.core.Node; +import java.util.ArrayList; +import java.util.List; + /** * Interface to adapt an external storage system to Corese. - * * {@code DataManagerRead} for {@code select where} SPARQL queries. - * * This interface is not used by the internal Corese graph, specific DataBroker * directly process it. * @@ -54,15 +52,12 @@ default Edge find(Edge edge) { /** * Returns an {@link Iterable} over all {@link Edge}s in the graph that match * the supplied criteria. - * * If several edges have the same subject, predicate and object then only one is * returned. - * * If the size of the contexts in parameter is not equal to 1, then the value of * Graph of Edges returned does not matter. It can be for example * {@code http://ns.inria.fr/corese.core.kgram/default}. Be careful, the value of * Graph of Edges cannot be {@code null}. - * * All {@code null} in context list are ignored. E.g. If the list contains only * null, it is handled as an empty list. * @@ -85,18 +80,17 @@ default Iterable getEdges(Node subject, Node predicate, Node object, List< // with condition edge.index >= index // use case: rule engine transitive closure ClosureDataManager + @SuppressWarnings("unused") default Iterable getEdges(Node subject, Node predicate, Node object, List contexts, int oper, int index) { return getEdges(subject, predicate, object, contexts); } - - // triple exist in any graph + + // triple exists in any graph default boolean exist(Node subject, Node predicate, Node object) { - for (Edge edge : getEdges(subject, predicate, object, null)) { - return true; - } - return false; + return getEdges(subject, predicate, object, null).iterator().hasNext(); } + /** * Returns an {@link Iterable} over all predicates of edges that match the * context without duplicates. diff --git a/src/main/java/fr/inria/corese/core/storage/api/dataManager/DataManagerUpdate.java b/src/main/java/fr/inria/corese/core/storage/api/dataManager/DataManagerUpdate.java index 3c00f8b70..291f082af 100644 --- a/src/main/java/fr/inria/corese/core/storage/api/dataManager/DataManagerUpdate.java +++ b/src/main/java/fr/inria/corese/core/storage/api/dataManager/DataManagerUpdate.java @@ -10,9 +10,7 @@ /** * Interface to adapt an external storage system to Corese. - * * {@code DataManagerUpdate} for {@code update} and {@code construct} queries. - * * This interface is also used by the internal Corese graph. * * @author Olivier Corby @@ -62,7 +60,6 @@ default Iterable insert(Node subject, Node predicate, Node object, List delete(Edge edge) { */ default boolean clear(List contexts, boolean silent) { Iterable deleted = this.delete(null, null, null, contexts); - Boolean succes = deleted.iterator().hasNext(); + boolean succes = deleted.iterator().hasNext(); if (silent) { return true; @@ -171,8 +168,7 @@ default boolean addGraph(Node source_context, Node target_context, boolean silen */ default boolean copyGraph(Node source_context, Node target_context, boolean silent) { this.clear(List.of(target_context), silent); - Boolean result = this.addGraph(source_context, target_context, silent); - return result; + return this.addGraph(source_context, target_context, silent); } /** @@ -186,7 +182,7 @@ default boolean copyGraph(Node source_context, Node target_context, boolean sile * true, else false. */ default boolean moveGraph(Node source_context, Node target_context, boolean silent) { - Boolean result = this.copyGraph(source_context, target_context, silent); + boolean result = this.copyGraph(source_context, target_context, silent); this.clear(List.of(source_context), silent); return result; } @@ -202,7 +198,6 @@ default void declareContext(Node context) { /** * Clear and undeclare a context in graph. * - * @param context */ default void unDeclareContext(Node context) { this.clear(List.of(context), false); From 2d77d73bfa94eb1a21b3018c88d9aba0572d706c Mon Sep 17 00:00:00 2001 From: "AD\\aabdoun" Date: Fri, 9 Jan 2026 09:44:47 +0100 Subject: [PATCH 2/8] Refactor DataManager architecture --- .../core/storage/CoreseGraphDataManager.java | 299 ++++--------- .../CoreseGraphDataManagerBuilder.java | 12 + .../corese/core/storage/DataManagerJava.java | 177 +++++--- .../storage/api/dataManager/DataManager.java | 6 - .../api/dataManager/DataManagerRead.java | 3 + .../api/dataManager/DataManagerUpdate.java | 4 + .../lifecycle/DataManagerLifecycle.java | 64 +++ .../dataManager/lifecycle/LifecycleState.java | 50 +++ .../operations/BulkOperations.java | 128 ++++++ .../operations/MetadataOperations.java | 70 +++ .../operations/MutationOperations.java | 99 ++++ .../operations/QueryOperations.java | 62 +++ .../support/config/DataManagerConfig.java | 180 ++++++++ .../exception/DataManagerException.java | 50 +++ .../support/exception/ErrorCode.java | 49 ++ .../support/model/EdgePattern.java | 223 +++++++++ .../support/model/GraphStatistics.java | 219 +++++++++ .../support/model/MutationResult.java | 379 ++++++++++++++++ .../transaction/IsolationLevel.java | 48 ++ .../dataManager/transaction/Transaction.java | 69 +++ .../transaction/TransactionManager.java | 69 +++ .../transaction/TransactionState.java | 42 ++ .../impl/lifecycle/LifecycleManagerImpl.java | 169 +++++++ .../impl/operations/BulkOperationsImpl.java | 422 ++++++++++++++++++ .../operations/MetadataOperationsImpl.java | 170 +++++++ .../operations/MutationOperationsImpl.java | 253 +++++++++++ .../impl/operations/QueryOperationsImpl.java | 171 +++++++ .../impl/transaction/TransactionImpl.java | 185 ++++++++ .../transaction/TransactionManagerImpl.java | 191 ++++++++ 29 files changed, 3602 insertions(+), 261 deletions(-) create mode 100644 src/main/java/fr/inria/corese/core/storage/api/dataManager/lifecycle/DataManagerLifecycle.java create mode 100644 src/main/java/fr/inria/corese/core/storage/api/dataManager/lifecycle/LifecycleState.java create mode 100644 src/main/java/fr/inria/corese/core/storage/api/dataManager/operations/BulkOperations.java create mode 100644 src/main/java/fr/inria/corese/core/storage/api/dataManager/operations/MetadataOperations.java create mode 100644 src/main/java/fr/inria/corese/core/storage/api/dataManager/operations/MutationOperations.java create mode 100644 src/main/java/fr/inria/corese/core/storage/api/dataManager/operations/QueryOperations.java create mode 100644 src/main/java/fr/inria/corese/core/storage/api/dataManager/support/config/DataManagerConfig.java create mode 100644 src/main/java/fr/inria/corese/core/storage/api/dataManager/support/exception/DataManagerException.java create mode 100644 src/main/java/fr/inria/corese/core/storage/api/dataManager/support/exception/ErrorCode.java create mode 100644 src/main/java/fr/inria/corese/core/storage/api/dataManager/support/model/EdgePattern.java create mode 100644 src/main/java/fr/inria/corese/core/storage/api/dataManager/support/model/GraphStatistics.java create mode 100644 src/main/java/fr/inria/corese/core/storage/api/dataManager/support/model/MutationResult.java create mode 100644 src/main/java/fr/inria/corese/core/storage/api/dataManager/transaction/IsolationLevel.java create mode 100644 src/main/java/fr/inria/corese/core/storage/api/dataManager/transaction/Transaction.java create mode 100644 src/main/java/fr/inria/corese/core/storage/api/dataManager/transaction/TransactionManager.java create mode 100644 src/main/java/fr/inria/corese/core/storage/api/dataManager/transaction/TransactionState.java create mode 100644 src/main/java/fr/inria/corese/core/storage/impl/lifecycle/LifecycleManagerImpl.java create mode 100644 src/main/java/fr/inria/corese/core/storage/impl/operations/BulkOperationsImpl.java create mode 100644 src/main/java/fr/inria/corese/core/storage/impl/operations/MetadataOperationsImpl.java create mode 100644 src/main/java/fr/inria/corese/core/storage/impl/operations/MutationOperationsImpl.java create mode 100644 src/main/java/fr/inria/corese/core/storage/impl/operations/QueryOperationsImpl.java create mode 100644 src/main/java/fr/inria/corese/core/storage/impl/transaction/TransactionImpl.java create mode 100644 src/main/java/fr/inria/corese/core/storage/impl/transaction/TransactionManagerImpl.java diff --git a/src/main/java/fr/inria/corese/core/storage/CoreseGraphDataManager.java b/src/main/java/fr/inria/corese/core/storage/CoreseGraphDataManager.java index 37190b4a6..56a1c16b4 100644 --- a/src/main/java/fr/inria/corese/core/storage/CoreseGraphDataManager.java +++ b/src/main/java/fr/inria/corese/core/storage/CoreseGraphDataManager.java @@ -1,252 +1,151 @@ package fr.inria.corese.core.storage; -import java.util.ArrayList; -import java.util.List; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import fr.inria.corese.core.Graph; import fr.inria.corese.core.producer.MetadataManager; import fr.inria.corese.core.storage.api.dataManager.DataManager; -import fr.inria.corese.core.kgram.api.core.Edge; -import fr.inria.corese.core.kgram.api.core.Node; -import fr.inria.corese.core.sparql.triple.parser.HashMapList; +import fr.inria.corese.core.storage.api.dataManager.lifecycle.DataManagerLifecycle; +import fr.inria.corese.core.storage.api.dataManager.operations.BulkOperations; +import fr.inria.corese.core.storage.api.dataManager.operations.MetadataOperations; +import fr.inria.corese.core.storage.api.dataManager.operations.MutationOperations; +import fr.inria.corese.core.storage.api.dataManager.operations.QueryOperations; +import fr.inria.corese.core.storage.api.dataManager.transaction.IsolationLevel; +import fr.inria.corese.core.storage.api.dataManager.transaction.TransactionManager; +import fr.inria.corese.core.storage.impl.lifecycle.LifecycleManagerImpl; +import fr.inria.corese.core.storage.impl.operations.BulkOperationsImpl; +import fr.inria.corese.core.storage.impl.operations.MetadataOperationsImpl; +import fr.inria.corese.core.storage.impl.operations.MutationOperationsImpl; +import fr.inria.corese.core.storage.impl.operations.QueryOperationsImpl; +import fr.inria.corese.core.storage.impl.transaction.TransactionManagerImpl; /** * DataManager for corese Graph for testing purpose */ public class CoreseGraphDataManager implements DataManager { - private static final Logger logger = LoggerFactory.getLogger(CoreseGraphDataManager.class); - static final String STORAGE_PATH = "http://ns.inria.fr/corese/dataset"; - private static final String DEBUG = "debug"; - List emptyNodeList; - List emptyEdgeList; - private Graph graph; + private final Graph graph; + private final LifecycleManagerImpl lifecycleManager; + private final QueryOperationsImpl queryOperations; + private final MetadataOperationsImpl metadataOperations; + private final MutationOperationsImpl mutationOperations; + private final BulkOperationsImpl bulkOperations; + private final TransactionManagerImpl transactionManager; private MetadataManager metadataManager; - private String path = STORAGE_PATH; - private boolean debug = false; /** - * Please use the CoreseGraphDataManagerBuilder to create a - * CoreseGraphDataManager. - * + * Protected constructor. + * Please use CoreseGraphDataManagerBuilder to create an instance. */ protected CoreseGraphDataManager() { - setGraph(new Graph()); - initlocal(); + this(new Graph()); } /** - * Please use the CoreseGraphDataManagerBuilder to create a - * CoreseGraphDataManager. - * + * Protected constructor with existing graph. + * Please use CoreseGraphDataManagerBuilder to create an instance. + * + * @param graph Existing graph instance */ - protected CoreseGraphDataManager(Graph g) { - setGraph(g); - initlocal(); - } - - void initlocal() { - emptyNodeList = new ArrayList<>(0); - emptyEdgeList = new ArrayList<>(0); - } - - @Override - public void startReadTransaction() { - getGraph().init(); - } - - @Override - public void startWriteTransaction() { - getGraph().init(); - } - - @Override - public void endWriteTransaction() { - getGraph().init(); - } - - public String getStoragePath() { - return getPath(); - } - - @Override - public void start(HashMapList map) { - if (map != null) { - init(map); - } - } - - @Override - public void init(HashMapList map) { - logger.info("Init data manager: {}", map); - if (map.containsKey(DEBUG)) { - setDebug(map.booleanValue(DEBUG)); - } - } - - // from.size == 1 -> named graph semantics - // else -> default graph semantics - @Override - public Iterable getEdges( - Node subject, Node predicate, Node object, List from) { - return getGraph().iterate(subject, predicate, object, from); - } - - @Override - public int graphSize() { - return getGraph().size(); - } - - @Override - public int countEdges(Node predicate) { - return getGraph().size(predicate); + protected CoreseGraphDataManager(Graph graph) { + this.graph = graph; + this.lifecycleManager = new LifecycleManagerImpl(this.graph); + this.queryOperations = new QueryOperationsImpl(this.graph); + this.metadataOperations = new MetadataOperationsImpl(this.graph); + this.mutationOperations = new MutationOperationsImpl(this.graph); + this.bulkOperations = new BulkOperationsImpl(this.graph); + this.transactionManager = new TransactionManagerImpl( + this.graph, + false, + IsolationLevel.READ_COMMITTED + ); } - /** - * Retrieve occurrence of query edge in target storage. - * Use case: edge is rdf star triple, purpose is to get its reference node - * if any - * - * @param edge The query edge to find in target storage - * @return The target edge if any + * Returns the transaction manager. + * + * @return Transaction manager */ - @Override - public Edge find(Edge edge) { - return getGraph().find(edge); - } - - // in practice, context is not used ... - @Override - public Iterable predicates(Node context) { - return getGraph().getSortedProperties(); - } - - @Override - public Iterable contexts() { - return getGraph().getGraphNodes(emptyNodeList); - } - - @Override - public Iterable getNodes(Node context) { - if (context == null) { - return getGraph().getNodeGraphIterator(); - } - return getGraph().getNodeGraphIterator(getGraph().getNode(context)); - } - - @Override - public String blankNode() { - return getGraph().newBlankID(); - } - - @Override - public Iterable insert(Node s, Node p, Node o, List contexts) { - return getGraph().insert(s, p, o, contexts); - } - - // @todo: rdf star - @Override - public Edge insert(Edge edge) { - return getGraph().insertEdgeWithTargetNode(edge); - } - - @Override - public Iterable delete(Node s, Node p, Node o, List contexts) { - return getGraph().delete(s, p, o, contexts); - } - - @Override - public Iterable delete(Edge edge) { - return getGraph().deleteEdgeWithTargetNode(edge); + public TransactionManager getTransactionManager() { + return transactionManager; } - - @Override - public boolean clear(List contexts, boolean silent) { - for (Node g : contexts) { - getGraph().clear(g.getLabel(), silent); - } - return true; - } - /** - * Removes all edges in graph. + * Returns the lifecycle manager. + * + * @return Lifecycle manager */ - @Override - public void clear() { - getGraph().clear(); - getGraph().dropGraphNames(); - } - - @Override - public boolean addGraph(Node source_context, Node target_context, boolean silent) { - return getGraph().add(source_context.getLabel(), target_context.getLabel(), silent); + public DataManagerLifecycle getLifecycle() { + return lifecycleManager; } - @Override - public boolean copyGraph(Node source_context, Node target_context, boolean silent) { - return getGraph().copy(source_context.getLabel(), target_context.getLabel(), silent); - } - @Override - public boolean moveGraph(Node source_context, Node target_context, boolean silent) { - return getGraph().move(source_context.getLabel(), target_context.getLabel(), silent); + /** + * Returns the query operations. + * + * @return Query operations + */ + public QueryOperations getQueryOperations() { + return queryOperations; } /** - * Declare a new context in graph. - * - * @param context New context to declare. + * Returns the metadata operations. + * + * @return Metadata operations */ - @Override - public void declareContext(Node context) { - getGraph().addGraphNode(context); + public MetadataOperations getMetadataOperations() { + return metadataOperations; } - @Override - public void unDeclareContext(Node context) { - this.clear(List.of(context), false); + /** + * Returns the mutation operations. + * + * @return Mutation operations + */ + public MutationOperations getMutationOperations() { + return mutationOperations; } - @Override - public void unDeclareAllContexts() { - this.clear(); + /** + * Returns the bulk operations. + * + * @return Bulk operations + */ + public BulkOperations getBulkOperations() { + return bulkOperations; } + /** + * Returns the underlying Graph. + * Direct graph access for advanced use cases. + * + * @return The graph + */ public Graph getGraph() { return graph; } - public void setGraph(Graph graph) { - this.graph = graph; + /** + * Checks if this DataManager has a MetadataManager. + * + * @return true if MetadataManager is set + */ + public boolean hasMetadataManager() { + return metadataManager != null; } - @Override + /** + * Gets the MetadataManager. + * + * @return MetadataManager or null + */ public MetadataManager getMetadataManager() { return metadataManager; } - @Override + /** + * Sets the MetadataManager. + * + * @param metadataManager MetadataManager to set + */ public void setMetadataManager(MetadataManager metadataManager) { this.metadataManager = metadataManager; } - - public String getPath() { - return path; - } - - public void setPath(String path) { - this.path = path; - } - - public boolean isDebug() { - return debug; - } - - public void setDebug(boolean debug) { - this.debug = debug; - } - -} +} \ No newline at end of file diff --git a/src/main/java/fr/inria/corese/core/storage/CoreseGraphDataManagerBuilder.java b/src/main/java/fr/inria/corese/core/storage/CoreseGraphDataManagerBuilder.java index a915fdf71..22513c993 100644 --- a/src/main/java/fr/inria/corese/core/storage/CoreseGraphDataManagerBuilder.java +++ b/src/main/java/fr/inria/corese/core/storage/CoreseGraphDataManagerBuilder.java @@ -8,8 +8,20 @@ public class CoreseGraphDataManagerBuilder implements DataManagerBuilder { private Graph graph; private boolean defGraph = false; + private boolean enableTransactions = false; + /** + * Enables transaction support. + * + * @param enable true to enable transactions + * @return this builder + */ + public CoreseGraphDataManagerBuilder withTransactions(boolean enable) { + this.enableTransactions = enable; + return this; + } + /** * Create a CoreseGraphDataManagerBuilder. */ diff --git a/src/main/java/fr/inria/corese/core/storage/DataManagerJava.java b/src/main/java/fr/inria/corese/core/storage/DataManagerJava.java index cabdd9755..b4e058b9c 100644 --- a/src/main/java/fr/inria/corese/core/storage/DataManagerJava.java +++ b/src/main/java/fr/inria/corese/core/storage/DataManagerJava.java @@ -23,14 +23,8 @@ import java.util.List; /** - * DataManager on top of json (or xml) document - * a) implemented in java as graph data manager - * path = path of insert where query that creates a graph (from json, xml, etc.) - * update query creates graph data manager - * b) implemented in ldscript when path contains ldscript (draft for testing) - * path = path of ldscript function definition - * 1- read (json) file with us:read() ldscript determine json file - * 2- iterate edges with ldscript function us:iterate() + * DataManager on top of JSON (or XML) document. + * */ public class DataManagerJava extends CoreseGraphDataManager { private static final Logger logger = LoggerFactory.getLogger(DataManagerJava.class); @@ -53,20 +47,30 @@ public class DataManagerJava extends CoreseGraphDataManager { private HashMapList map; private List load; - // path of insert where query that creates a graph (from json or xml) - // or - // path of ldscript us:read and us:iterate functions + // Local path field (since parent doesn't have setPath/getPath) + private final String path; + + /** + * Constructs a DataManagerJava with a path to query or LDScript functions. + * + * @param path Path to INSERT WHERE query or LDScript functions + */ public DataManagerJava(String path) { + super(); // Call parent constructor URLServer url = new URLServer(path); - // remove parameter if any - setPath(url.getServer()); + // Remove parameter if any + this.path = url.getServer(); setQueryPath(url.getServer()); } - // creation time in StorageFactory - @Override + /** + * Starts the data manager with configuration. + * Called at creation time in StorageFactory. + * + * @param map Configuration parameters + */ public void start(HashMapList map) { - logger.info("Start data manager: {}", getStoragePath()); + logger.info("Start data manager: {}", path); if (map == null) { init(); } else { @@ -77,9 +81,12 @@ public void start(HashMapList map) { } } - // path parameter map - // called by service store clause in ProviderService - @Override + /** + * Initializes with path parameter map. + * Called by service store clause in ProviderService. + * + * @param map Configuration parameters + */ public void init(HashMapList map) { localInit(map); } @@ -89,7 +96,12 @@ boolean localInit(HashMapList map) { return parameter(map); } - // return true when init() is performed + /** + * Processes parameters and returns true when init() is performed. + * + * @param map Configuration parameters + * @return true if initialization was performed + */ boolean parameter(HashMapList map) { setMap(map); String queryPath = map.getFirst(PATH); @@ -123,7 +135,9 @@ String clean(String str) { return str.replace("%20", " "); } - //@Override + /** + * Initializes based on mode (Graph or LDScript). + */ void init() { if (isLdscript()) { initldscript(); @@ -132,30 +146,34 @@ void init() { } } - // create graph from json using update query located at path - // set this data manager graph + /** + * Initializes in Graph mode: creates graph from JSON using UPDATE query. + */ void initgraph() { logger.info("Mode graph"); - // graph to be created by update query - setGraph(Graph.create()); - //setQueryProcess(QueryProcess.create(getGraph())); + // Graph to be created by update query + // Create new graph and reinitialize parent components + Graph newGraph = Graph.create(); + setQueryProcess(QueryProcess.create(this)); QueryLoad ql = QueryLoad.create(); - Load ld = Load.create(getGraph()); + Load ld = Load.create(newGraph); ld.setDataManager(this); - // temporary authorize xt:read file to read e.g. json document - //Level read = Access.setValue(Feature.READ, Level.DEFAULT); - // Level readFile = Access.setValue(Feature.READ_FILE, Level.DEFAULT); + + // Temporarily authorize xt:read file to read e.g. json document // authorize xt:read() because accept list is empty during this initialization Access.setDefaultResultWhenEmptyAccept(true); + try { - if (getLoad()!=null) { + // Load data files if specified + if (getLoad() != null) { for (String name : getLoad()) { logger.info("Load {}", name); ld.parse(name); } } - // query who creates rdf graph (from json) + + // Get query (from path or direct string) String q; if (getQueryPath() != null) { logger.info("Load {}", getQueryPath()); @@ -166,63 +184,105 @@ void initgraph() { return; } + // Format query with parameters if provided if (getMap() != null && getMap().containsKey(PARAM)) { q = String.format(q, getMap().getFirst(PARAM)); } + logger.info("Process query:\n{}", q); - // update query creates rdf graph (from json) - // this is graph of current DataManager - Mappings map = getQueryProcess().query(q); - if (map.getGraph() != null) { - // construct where query - setGraph((Graph) map.getGraph()); + + // Execute UPDATE query to create RDF graph (from JSON) + // This is the graph of current DataManager + Mappings mappings = getQueryProcess().query(q); + if (mappings.getGraph() != null) { + // CONSTRUCT WHERE query result + newGraph = (Graph) mappings.getGraph(); } - getGraph().init(); + newGraph.init(); + + logger.info("Graph initialized with {} edges", newGraph.size()); + } catch (LoadException | EngineException ex) { - logger.error(ex.getMessage()); - } - finally { + logger.error("Failed to initialize graph mode: {}", ex.getMessage()); + } finally { Access.setDefaultResultWhenEmptyAccept(false); } } - // load json using ldscript us:read() function - // defined at path - // iterate edges from json using ldscript us:iterate() function + /** + * Initializes in LDScript mode: prepares for JSON iteration. + */ void initldscript() { - logger.info("Mode json"); + logger.info("Mode ldscript"); try { - setGraph(Graph.create()); setQueryProcess(QueryProcess.create(Graph.create())); + + // Import LDScript functions getQueryProcess().imports(getQueryPath()); + + // Read JSON document using LDScript us:read() function setJsonDocument(getQueryProcess().funcall(readFunction)); + + // Joker for null pattern matching joker = DatatypeMap.newInstance(GraphSpecificFunction.JOKER); + + logger.info("LDScript initialized, JSON document loaded"); + } catch (EngineException ex) { - logger.error(ex.getMessage()); + logger.error("Failed to initialize ldscript mode: {}", ex.getMessage()); } } - @Override + /** + * Gets edges with LDScript support. + * + * @param s Subject (null for any) + * @param p Predicate (null for any) + * @param o Object (null for any) + * @param graphList Contexts + * @return Iterable of edges + */ public Iterable getEdges(Node s, Node p, Node o, List graphList) { if (isLdscript()) { return iterateJson(s, p, o); } - return super.getEdges(s, p, o, graphList); + // In graph mode, use QueryOperations from parent + return getGraph().iterate(s, p, o, graphList); } + /** + * Iterates over JSON document using LDScript us:iterate() function. + * + * @param s Subject pattern (null for any) + * @param p Predicate pattern (null for any) + * @param o Object pattern (null for any) + * @return Iterable of matching edges + */ Iterable iterateJson(Node s, Node p, Node o) { try { - IDatatype dt = getQueryProcess().funcall(iterateFunction, getJsonDocument(), value(s), value(p), value(o)); + IDatatype dt = getQueryProcess().funcall( + iterateFunction, + getJsonDocument(), + value(s), + value(p), + value(o) + ); if (dt == null) { return new ArrayList<>(0); } return cast(dt); } catch (EngineException ex) { - logger.error(ex.getMessage()); + logger.error("Failed to iterate JSON: {}", ex.getMessage()); } return new ArrayList<>(0); } + /** + * Converts Node to IDatatype value, or joker if null. + * + * @param n Node to convert + * @return IDatatype value or joker + */ IDatatype value(Node n) { if (n == null) { return joker; @@ -230,7 +290,12 @@ IDatatype value(Node n) { return n.getDatatypeValue(); } - // list of triple reference, result of triple(s, p, o) + /** + * Converts IDatatype list to Edge list. + * + * @param list IDatatype list of triple references + * @return List of edges + */ Iterable cast(IDatatype list) { ArrayList edgeList = new ArrayList<>(); for (IDatatype dt : list) { @@ -239,7 +304,6 @@ Iterable cast(IDatatype list) { return edgeList; } - public QueryProcess getQueryProcess() { return queryProcess; } @@ -296,4 +360,7 @@ public void setLoad(List load) { this.load = load; } -} + public String getPath() { + return path; + } +} \ No newline at end of file diff --git a/src/main/java/fr/inria/corese/core/storage/api/dataManager/DataManager.java b/src/main/java/fr/inria/corese/core/storage/api/dataManager/DataManager.java index f839d5dd0..2a5ee6f51 100644 --- a/src/main/java/fr/inria/corese/core/storage/api/dataManager/DataManager.java +++ b/src/main/java/fr/inria/corese/core/storage/api/dataManager/DataManager.java @@ -13,9 +13,6 @@ */ public interface DataManager extends DataManagerRead, DataManagerUpdate { - /* ****************** - * MetaDataManager * - *******************/ /** * Indicates whether or not this DataManage has a MetaDataManager. @@ -70,9 +67,6 @@ default void init(HashMapList map) { default void setRuleDataManager(boolean b) { } - /* *************** - * Transactions * - ****************/ /** diff --git a/src/main/java/fr/inria/corese/core/storage/api/dataManager/DataManagerRead.java b/src/main/java/fr/inria/corese/core/storage/api/dataManager/DataManagerRead.java index 8e3f6e4f9..05c7d53f9 100644 --- a/src/main/java/fr/inria/corese/core/storage/api/dataManager/DataManagerRead.java +++ b/src/main/java/fr/inria/corese/core/storage/api/dataManager/DataManagerRead.java @@ -33,6 +33,7 @@ default int graphSize() { * @param predicate The predicate to count. * @return Number of edges with a specific predicate. */ + @SuppressWarnings("unused") default int countEdges(Node predicate) { return 0; } @@ -99,6 +100,7 @@ default boolean exist(Node subject, Node predicate, Node object) { * @return An {@link Iterable} over all predicates of edges that match the * context. */ + @SuppressWarnings("unused") default Iterable predicates(Node context) { return new ArrayList<>(0); } @@ -110,6 +112,7 @@ default Iterable predicates(Node context) { * @param context Context to match, {@code null} to match with any contexts. * @return An {@link Iterable} over all node in graph that match the context. */ + @SuppressWarnings("unused") default Iterable getNodes(Node context) { return new ArrayList<>(0); } diff --git a/src/main/java/fr/inria/corese/core/storage/api/dataManager/DataManagerUpdate.java b/src/main/java/fr/inria/corese/core/storage/api/dataManager/DataManagerUpdate.java index 291f082af..3feb469ac 100644 --- a/src/main/java/fr/inria/corese/core/storage/api/dataManager/DataManagerUpdate.java +++ b/src/main/java/fr/inria/corese/core/storage/api/dataManager/DataManagerUpdate.java @@ -70,6 +70,7 @@ default Iterable insert(Node subject, Node predicate, Node object, List delete(Node subject, Node predicate, Node object, List contexts) { return null; } @@ -152,6 +154,7 @@ default void clear() { * @return True if the graph has been modified or {@code silent} parameter is * true, else false. */ + @SuppressWarnings("unused") default boolean addGraph(Node source_context, Node target_context, boolean silent) { return false; } @@ -192,6 +195,7 @@ default boolean moveGraph(Node source_context, Node target_context, boolean sile * * @param context New context to declare. */ + @SuppressWarnings("unused") default void declareContext(Node context) { } diff --git a/src/main/java/fr/inria/corese/core/storage/api/dataManager/lifecycle/DataManagerLifecycle.java b/src/main/java/fr/inria/corese/core/storage/api/dataManager/lifecycle/DataManagerLifecycle.java new file mode 100644 index 000000000..6d92dd303 --- /dev/null +++ b/src/main/java/fr/inria/corese/core/storage/api/dataManager/lifecycle/DataManagerLifecycle.java @@ -0,0 +1,64 @@ +package fr.inria.corese.core.storage.api.dataManager.lifecycle; + +import fr.inria.corese.core.storage.api.dataManager.support.config.DataManagerConfig; +import fr.inria.corese.core.storage.api.dataManager.support.exception.DataManagerException; + +/** + * Lifecycle management for the DataManager. + */ +public interface DataManagerLifecycle { + + /** + * Initializes the DataManager with the provided configuration. + * + * @param config DataManager configuration (must not be null) + * @throws DataManagerException if initialization fails + * @throws IllegalStateException if already initialized + * @throws IllegalArgumentException if config is null + */ + void initialize(DataManagerConfig config) throws DataManagerException; + + /** + * Checks if the DataManager is initialized and ready to use. + * + * @return true if initialized (RUNNING state), false otherwise + */ + boolean isInitialized(); + + /** + * Cleanly shuts down the DataManager and releases all resources. + * + * @throws DataManagerException if shutdown fails + * @throws IllegalStateException if not initialized + */ + void shutdown() throws DataManagerException; + + /** + * Restarts the DataManager with a new configuration. + * + * @param config New configuration + * @throws DataManagerException if restart fails + */ + default void restart(DataManagerConfig config) throws DataManagerException { + if (isInitialized()) { + shutdown(); + } + initialize(config); + } + + /** + * Returns the current lifecycle state. + * + * @return Current state + */ + LifecycleState getState(); + + /** + * Returns the currently used configuration. + * + * @return Current configuration, or null if not initialized + */ + DataManagerConfig getConfig(); + + +} \ No newline at end of file diff --git a/src/main/java/fr/inria/corese/core/storage/api/dataManager/lifecycle/LifecycleState.java b/src/main/java/fr/inria/corese/core/storage/api/dataManager/lifecycle/LifecycleState.java new file mode 100644 index 000000000..e812231fb --- /dev/null +++ b/src/main/java/fr/inria/corese/core/storage/api/dataManager/lifecycle/LifecycleState.java @@ -0,0 +1,50 @@ +package fr.inria.corese.core.storage.api.dataManager.lifecycle; + +/** + * Possible lifecycle states of the DataManager. + */ +public enum LifecycleState { + /** + * The DataManager is not yet initialized + */ + NOT_INITIALIZED("Not initialized"), + + /** + * The DataManager is initializing + */ + INITIALIZING("Initializing"), + + /** + * The DataManager is operational and ready + */ + RUNNING("Running"), + + /** + * The DataManager is shutting down + */ + SHUTTING_DOWN("Shutting down"), + + /** + * The DataManager has been shut down + */ + SHUTDOWN("Shutdown"); + + private final String description; + + LifecycleState(String description) { + this.description = description; + } + + public String getDescription() { + return description; + } + + /** + * Checks if the DataManager can be used in this state. + * + * @return true if usable (RUNNING) + */ + public boolean isUsable() { + return this == RUNNING; + } +} diff --git a/src/main/java/fr/inria/corese/core/storage/api/dataManager/operations/BulkOperations.java b/src/main/java/fr/inria/corese/core/storage/api/dataManager/operations/BulkOperations.java new file mode 100644 index 000000000..69ac84361 --- /dev/null +++ b/src/main/java/fr/inria/corese/core/storage/api/dataManager/operations/BulkOperations.java @@ -0,0 +1,128 @@ +package fr.inria.corese.core.storage.api.dataManager.operations; + +import fr.inria.corese.core.kgram.api.core.Edge; +import fr.inria.corese.core.kgram.api.core.Node; +import fr.inria.corese.core.storage.api.dataManager.support.exception.DataManagerException; +import fr.inria.corese.core.storage.api.dataManager.support.model.EdgePattern; +import fr.inria.corese.core.storage.api.dataManager.support.model.MutationResult; + +import java.util.List; + +/** + * Bulk mutation operations for batch processing. + */ +public interface BulkOperations { + + /** + * Inserts multiple edges in a single batch operation. + * + * @param edges List of edges to insert + * @return Bulk mutation result with statistics + * @throws DataManagerException if operation fails + * @throws IllegalArgumentException if edges is null or empty + */ + MutationResult insertBatch(List edges) throws DataManagerException; + + /** + * Deletes multiple edges in a single batch operation. + * More efficient than calling deleteEdge() multiple times. + * + * @param edges List of edges to delete + * @return Bulk mutation result with statistics + * @throws DataManagerException if operation fails + * @throws IllegalArgumentException if edges is null or empty + */ + MutationResult deleteBatch(List edges) throws DataManagerException; + + /** + * Deletes all edges matching the given pattern. + * + * @param pattern Pattern to match edges for deletion + * @return Bulk mutation result with deleted edges + * @throws DataManagerException if operation fails + * @throws IllegalArgumentException if pattern is null + */ + MutationResult deleteByPattern(EdgePattern pattern) throws DataManagerException; + + /** + * Clears (deletes all edges from) specific contexts. + * + * @param contexts List of contexts to clear (empty or null for all) + * @param silent If true, don't fail if context doesn't exist + * @return Bulk mutation result + * @throws DataManagerException if operation fails + */ + MutationResult clearContexts(List contexts, boolean silent) throws DataManagerException; + + /** + * Clears all contexts (deletes entire graph). + * + * @return Bulk mutation result with all deleted edges + * @throws DataManagerException if operation fails + */ + MutationResult clearAll() throws DataManagerException; + + /** + * Adds edges from source context to target context. + * Edges remain in source context. + * + * @param sourceContext Source context + * @param targetContext Target context + * @param silent If true, don't fail if source doesn't exist + * @return Bulk mutation result + * @throws DataManagerException if operation fails + * @throws IllegalArgumentException if contexts are null + */ + MutationResult addGraph(Node sourceContext, Node targetContext, boolean silent) + throws DataManagerException; + + /** + * Copies edges from source context to target context. + * Replaces all edges in target context. + * + * @param sourceContext Source context + * @param targetContext Target context + * @param silent If true, don't fail if source doesn't exist + * @return Bulk mutation result + * @throws DataManagerException if operation fails + * @throws IllegalArgumentException if contexts are null + */ + MutationResult copyGraph(Node sourceContext, Node targetContext, boolean silent) + throws DataManagerException; + + /** + * Moves edges from source context to target context. + * Removes edges from source context. + * + * @param sourceContext Source context + * @param targetContext Target context + * @param silent If true, don't fail if source doesn't exist + * @return Bulk mutation result + * @throws DataManagerException if operation fails + * @throws IllegalArgumentException if contexts are null + */ + MutationResult moveGraph(Node sourceContext, Node targetContext, boolean silent) + throws DataManagerException; + + /** + * Declares (creates) a context without adding edges. + * Useful for pre-creating named graphs. + * + * @param context Context to declare + * @return Mutation result + * @throws DataManagerException if operation fails + * @throws IllegalArgumentException if context is null + */ + MutationResult declareContext(Node context) throws DataManagerException; + + /** + * Undeclares (deletes) a context and all its edges. + * + * @param context Context to undeclare + * @return Bulk mutation result with deleted edges + * @throws DataManagerException if operation fails + * @throws IllegalArgumentException if context is null + */ + MutationResult undeclareContext(Node context) throws DataManagerException; + +} \ No newline at end of file diff --git a/src/main/java/fr/inria/corese/core/storage/api/dataManager/operations/MetadataOperations.java b/src/main/java/fr/inria/corese/core/storage/api/dataManager/operations/MetadataOperations.java new file mode 100644 index 000000000..770fc32b0 --- /dev/null +++ b/src/main/java/fr/inria/corese/core/storage/api/dataManager/operations/MetadataOperations.java @@ -0,0 +1,70 @@ +package fr.inria.corese.core.storage.api.dataManager.operations; + +import fr.inria.corese.core.kgram.api.core.Node; +import fr.inria.corese.core.storage.api.dataManager.support.exception.DataManagerException; +import fr.inria.corese.core.storage.api.dataManager.support.model.GraphStatistics; +import java.util.Set; + +/** + * Metadata operations for the graph. + * Provides access to graph structure information (predicates, nodes, contexts, statistics). + * + */ +public interface MetadataOperations { + + /** + * Returns all predicates in the specified context. + * + * @param context Context to query (null for all contexts) + * @return Set of predicates (unmodifiable) + * @throws DataManagerException if query fails + */ + Set getPredicates(Node context) throws DataManagerException; + + /** + * Returns all predicates in all contexts. + * Convenience method equivalent to getPredicates(null). + * + * @return Set of all predicates (unmodifiable) + * @throws DataManagerException if query fails + */ + default Set getAllPredicates() throws DataManagerException { + return getPredicates(null); + } + + /** + * Returns all nodes (subjects and objects) in the specified context. + * + * @param context Context to query (null for all contexts) + * @return Set of nodes (unmodifiable) + * @throws DataManagerException if query fails + */ + Set getNodes(Node context) throws DataManagerException; + + /** + * Returns all nodes in all contexts. + * Convenience method equivalent to getNodes(null). + * + * @return Set of all nodes (unmodifiable) + * @throws DataManagerException if query fails + */ + default Set getAllNodes() throws DataManagerException { + return getNodes(null); + } + + /** + * Returns all contexts (named graphs). + * + * @return Set of context nodes (unmodifiable) + * @throws DataManagerException if query fails + */ + Set getContexts() throws DataManagerException; + + /** + * Returns statistics about the graph content and structure. + * + * @return Graph statistics + * @throws DataManagerException if statistics cannot be computed + */ + GraphStatistics getStatistics() throws DataManagerException; +} \ No newline at end of file diff --git a/src/main/java/fr/inria/corese/core/storage/api/dataManager/operations/MutationOperations.java b/src/main/java/fr/inria/corese/core/storage/api/dataManager/operations/MutationOperations.java new file mode 100644 index 000000000..8374408ca --- /dev/null +++ b/src/main/java/fr/inria/corese/core/storage/api/dataManager/operations/MutationOperations.java @@ -0,0 +1,99 @@ +package fr.inria.corese.core.storage.api.dataManager.operations; + +import fr.inria.corese.core.kgram.api.core.Edge; +import fr.inria.corese.core.kgram.api.core.Node; +import fr.inria.corese.core.storage.api.dataManager.support.exception.DataManagerException; +import fr.inria.corese.core.storage.api.dataManager.support.model.MutationResult; + +import java.util.List; + +/** + * mutation operations. + */ +public interface MutationOperations { + + /** + * Inserts a single edge. + * + * @param edge Edge to insert + * @return Mutation result + * @throws DataManagerException if operation fails + * @throws IllegalArgumentException if edge is null + */ + MutationResult insertEdge(Edge edge) throws DataManagerException; + + /** + * Inserts an edge with explicit subject, predicate, object, and contexts. + * + * @param subject Subject node + * @param predicate Predicate node + * @param object Object node + * @param contexts List of contexts (null or empty for default graph) + * @throws DataManagerException if operation fails + * @throws IllegalArgumentException if subject, predicate, or object is null + */ + MutationResult insertEdge(Node subject, Node predicate, Node object, List contexts) + throws DataManagerException; + + /** + * Deletes a single edge. + * + * @param edge Edge to delete + * @return Mutation result + * @throws DataManagerException if operation fails + * @throws IllegalArgumentException if edge is null + */ + MutationResult deleteEdge(Edge edge) throws DataManagerException; + + /** + * Deletes edges matching the given pattern. + * + * @param subject Subject node (null for any) + * @param predicate Predicate node (null for any) + * @param object Object node (null for any) + * @param contexts List of contexts (null or empty for all) + * @return Mutation result (bulk result with all deleted edges) + * @throws DataManagerException if operation fails + */ + MutationResult deleteEdges(Node subject, Node predicate, Node object, List contexts) + throws DataManagerException; + + /** + * Updates an edge (delete + insert in one operation). + * + * @param oldEdge Edge to delete + * @param newEdge Edge to insert + * @return Mutation result + * @throws DataManagerException if operation fails + * @throws IllegalArgumentException if oldEdge or newEdge is null + */ + default MutationResult updateEdge(Edge oldEdge, Edge newEdge) throws DataManagerException { + if (oldEdge == null || newEdge == null) { + throw new IllegalArgumentException("Both oldEdge and newEdge must be non-null"); + } + + // Simple implementation: delete then insert + // In a transactional system, this would be atomic + MutationResult deleteResult = deleteEdge(oldEdge); + if (deleteResult.isFailure()) { + return deleteResult; + } + + MutationResult insertResult = insertEdge(newEdge); + if (insertResult.isFailure()) { + return MutationResult.failure( + "Insert failed after delete: " + insertResult.getMessage() + ); + } + + return insertResult; + } + + /** + * Generates a new blank node identifier. + * + * @return New blank node ID + * @throws DataManagerException if generation fails + */ + String generateBlankNode() throws DataManagerException; +} \ No newline at end of file diff --git a/src/main/java/fr/inria/corese/core/storage/api/dataManager/operations/QueryOperations.java b/src/main/java/fr/inria/corese/core/storage/api/dataManager/operations/QueryOperations.java new file mode 100644 index 000000000..48c0fe65c --- /dev/null +++ b/src/main/java/fr/inria/corese/core/storage/api/dataManager/operations/QueryOperations.java @@ -0,0 +1,62 @@ +package fr.inria.corese.core.storage.api.dataManager.operations; + +import fr.inria.corese.core.kgram.api.core.Edge; +import fr.inria.corese.core.storage.api.dataManager.support.exception.DataManagerException; +import fr.inria.corese.core.storage.api.dataManager.support.model.EdgePattern; + +import java.util.stream.Stream; + +/** + * Query operations for reading edges. + * Provides modern Stream-based API for querying the graph. + */ +public interface QueryOperations { + + /** + * Queries edges matching the given pattern. + * + * @param pattern Edge pattern to match + * @return Stream of matching edges (must be closed) + * @throws DataManagerException if query fails + * @throws IllegalArgumentException if pattern is null + */ + Stream query(EdgePattern pattern) throws DataManagerException; + + /** + * Counts edges matching the given pattern. + * More efficient than query().count() as it doesn't need to load edges. + * + * @param pattern Edge pattern to match + * @return Number of matching edges + * @throws DataManagerException if count fails + * @throws IllegalArgumentException if pattern is null + */ + long count(EdgePattern pattern) throws DataManagerException; + + /** + * Checks if at least one edge matches the given pattern. + * More efficient than query().findAny() as it can stop after first match. + * + * @param pattern Edge pattern to match + * @return true if at least one edge matches + * @throws DataManagerException if check fails + * @throws IllegalArgumentException if pattern is null + */ + boolean exists(EdgePattern pattern) throws DataManagerException; + + /** + * Finds a specific edge (for RDF-star support). + * Use case: edge is an RDF-star triple, purpose is to get its reference node if any. + * + * @param edge The query edge to find in target storage + * @return The target edge if found, otherwise the input edge + * @throws DataManagerException find fails + * @throws IllegalArgumentException if edge is null + */ + default Edge find(Edge edge) throws DataManagerException { + if (edge == null) { + throw new IllegalArgumentException("Edge cannot be null"); + } + return edge; + } +} \ No newline at end of file diff --git a/src/main/java/fr/inria/corese/core/storage/api/dataManager/support/config/DataManagerConfig.java b/src/main/java/fr/inria/corese/core/storage/api/dataManager/support/config/DataManagerConfig.java new file mode 100644 index 000000000..0de1c8631 --- /dev/null +++ b/src/main/java/fr/inria/corese/core/storage/api/dataManager/support/config/DataManagerConfig.java @@ -0,0 +1,180 @@ +package fr.inria.corese.core.storage.api.dataManager.support.config; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +/** + * Typed configuration for the DataManager. + */ +public final class DataManagerConfig { + + private final Map properties; + private final boolean transactionSupport; + private final String storagePath; + private final boolean debug; + + /** + * Private constructor - use the Builder. + */ + private DataManagerConfig(Builder builder) { + this.properties = Map.copyOf(builder.properties); + this.transactionSupport = builder.transactionSupport; + this.storagePath = builder.storagePath; + this.debug = builder.debug; + } + + /** + * Returns a property. + * + * @param key Property key + * @return Property value, or Optional.empty() if absent + */ + public Optional getProperty(String key) { + return Optional.ofNullable(properties.get(key)); + } + + /** + * Returns a property with a specific type. + * + * @param Property type + * @param key Property key + * @param type Expected type class + * @return Typed value, or Optional.empty() if absent or wrong type + */ + @SuppressWarnings("unchecked") + public Optional getProperty(String key, Class type) { + Object value = properties.get(key); + if (type.isInstance(value)) { + return Optional.of((T) value); + } + return Optional.empty(); + } + + /** + * Returns all properties. + * + * @return Immutable map of properties + */ + public Map getProperties() { + return properties; + } + + /** + * Returns the storage path. + * + * @return Storage path + */ + public String getStoragePath() { + return storagePath; + } + + /** + * Indicates whether debug mode is enabled. + * + * @return true if debug enabled + */ + public boolean isDebug() { + return debug; + } + + /** + * Creates a new builder. + * + * @return New builder instance + */ + public static Builder builder() { + return new Builder(); + } + + @Override + public String toString() { + return "DataManagerConfig{" + + "storagePath='" + storagePath + '\'' + + ", transactionSupport=" + transactionSupport + + ", debug=" + debug + + ", properties=" + properties + + '}'; + } + + /** + * Builder for DataManagerConfig. + */ + public static final class Builder { + private final Map properties = new HashMap<>(); + private boolean transactionSupport = false; + private String storagePath = "http://ns.inria.fr/corese/dataset"; + private boolean debug = false; + + private Builder() { + } + + /** + * Adds a generic property. + * + * @param key Property key + * @param value Property value + * @return This builder (for chaining) + */ + public Builder property(String key, Object value) { + if (key != null && value != null) { + properties.put(key, value); + } + return this; + } + + /** + * Enables or disables transaction support. + * + * @param enable true to enable + * @return This builder (for chaining) + */ + public Builder enableTransactions(boolean enable) { + this.transactionSupport = enable; + return this; + } + + /** + * Enables or disables transaction support (alias for enableTransactions). + * + * @param enable true to enable + * @return This builder (for chaining) + */ + public Builder transactionSupport(boolean enable) { + return enableTransactions(enable); + } + + /** + * Sets the storage path. + * + * @param path Storage path + * @return This builder (for chaining) + */ + public Builder storagePath(String path) { + if (path != null && !path.isEmpty()) { + this.storagePath = path; + } + return this; + } + + /** + * Enables or disables debug mode. + * + * @param debug true to enable + * @return This builder (for chaining) + */ + public Builder debug(boolean debug) { + this.debug = debug; + return this; + } + + /** + * Builds the DataManagerConfig instance. + * + * @return New configured instance + */ + public DataManagerConfig build() { + return new DataManagerConfig(this); + } + } +} \ No newline at end of file diff --git a/src/main/java/fr/inria/corese/core/storage/api/dataManager/support/exception/DataManagerException.java b/src/main/java/fr/inria/corese/core/storage/api/dataManager/support/exception/DataManagerException.java new file mode 100644 index 000000000..4eb70da90 --- /dev/null +++ b/src/main/java/fr/inria/corese/core/storage/api/dataManager/support/exception/DataManagerException.java @@ -0,0 +1,50 @@ +package fr.inria.corese.core.storage.api.dataManager.support.exception; + +/** + * Specific exception for DataManager operations. + */ +public class DataManagerException extends Exception { + + private final ErrorCode code; + + /** + * Constructs an exception with a code and message. + * + * @param code Error code + * @param message Descriptive message + */ + public DataManagerException(ErrorCode code, String message) { + super(message); + this.code = code; + } + + /** + * Constructs an exception with a code, message and cause. + * + * @param code Error code + * @param message Descriptive message + * @param cause Original exception + */ + public DataManagerException(ErrorCode code, String message, Throwable cause) { + super(message, cause); + this.code = code; + } + + /** + * Returns the error code. + * + * @return Error code + */ + public ErrorCode getCode() { + return code; + } + + @Override + public String toString() { + return "DataManagerException{" + + "code=" + code + + ", message='" + getMessage() + '\'' + + '}'; + } + +} \ No newline at end of file diff --git a/src/main/java/fr/inria/corese/core/storage/api/dataManager/support/exception/ErrorCode.java b/src/main/java/fr/inria/corese/core/storage/api/dataManager/support/exception/ErrorCode.java new file mode 100644 index 000000000..e33189555 --- /dev/null +++ b/src/main/java/fr/inria/corese/core/storage/api/dataManager/support/exception/ErrorCode.java @@ -0,0 +1,49 @@ +package fr.inria.corese.core.storage.api.dataManager.support.exception; + +/** + * Error codes for DataManager operations. + */ +public enum ErrorCode { + /** Initialization failure */ + INITIALIZATION_FAILED("INIT_FAIL", "Initialization failed"), + + /** Shutdown failure */ + SHUTDOWN_FAILED("SHUTDOWN_FAIL", "Shutdown failed"), + + /** Transaction error */ + TRANSACTION_ERROR("TX_ERROR", "Transaction error"), + + /** Query failure */ + QUERY_FAILED("QUERY_FAIL", "Query failed"), + + /** Mutation failure */ + MUTATION_FAILED("MUTATION_FAIL", "Mutation failed"), + + /** Invalid pattern */ + INVALID_PATTERN("INVALID_PATTERN", "Invalid pattern"), + + /** Context not found */ + CONTEXT_NOT_FOUND("CTX_NOT_FOUND", "Context not found"), + + /** Unsupported operation */ + UNSUPPORTED_OPERATION("UNSUPPORTED", "Operation not supported"), + + /** Invalid state */ + INVALID_STATE("INVALID_STATE", "Invalid state"); + + private final String code; + private final String description; + + ErrorCode(String code, String description) { + this.code = code; + this.description = description; + } + + public String getCode() { + return code; + } + + public String getDescription() { + return description; + } +} diff --git a/src/main/java/fr/inria/corese/core/storage/api/dataManager/support/model/EdgePattern.java b/src/main/java/fr/inria/corese/core/storage/api/dataManager/support/model/EdgePattern.java new file mode 100644 index 000000000..6c6295584 --- /dev/null +++ b/src/main/java/fr/inria/corese/core/storage/api/dataManager/support/model/EdgePattern.java @@ -0,0 +1,223 @@ +package fr.inria.corese.core.storage.api.dataManager.support.model; + +import fr.inria.corese.core.kgram.api.core.Node; + +import java.util.List; +import java.util.Objects; + +/** + * Pattern for matching edges in queries. + */ +public final class EdgePattern { + + private final Node subject; + private final Node predicate; + private final Node object; + private final List contexts; + + /** + * Private constructor - use Builder. + */ + private EdgePattern(Builder builder) { + this.subject = builder.subject; + this.predicate = builder.predicate; + this.object = builder.object; + this.contexts = builder.contexts != null + ? List.copyOf(builder.contexts) + : null; + } + + /** + * Returns the subject node (null means any). + * + * @return Subject node or null + */ + public Node getSubject() { + return subject; + } + + /** + * Returns the predicate node (null means any). + * + * @return Predicate node or null + */ + public Node getPredicate() { + return predicate; + } + + /** + * Returns the object node (null means any). + * + * @return Object node or null + */ + public Node getObject() { + return object; + } + + /** + * Returns the contexts to search in (null or empty means all). + * + * @return List of contexts or null + */ + public List getContexts() { + return contexts; + } + + /** + * Checks if this pattern has a subject constraint. + * + * @return true if subject is specified + */ + public boolean isSubject() { + return subject == null; + } + + /** + * Checks if this pattern has a predicate constraint. + * + * @return true if predicate is specified + */ + public boolean isPredicate() { + return predicate == null; + } + + /** + * Checks if this pattern has an object constraint. + * + * @return true if object is specified + */ + public boolean isObject() { + return object == null; + } + + /** + * Checks if this pattern has context constraints. + * + * @return true if contexts are specified + */ + public boolean isContexts() { + return contexts == null || contexts.isEmpty(); + } + + /** + * Checks if this pattern matches everything (no constraints). + * + * @return true if no constraints + */ + public boolean matchesAll() { + return isSubject() && isPredicate() && isObject() && isContexts(); + } + + /** + * Creates a new builder. + * + * @return New builder instance + */ + public static Builder builder() { + return new Builder(); + } + + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + EdgePattern that = (EdgePattern) o; + return Objects.equals(subject, that.subject) && + Objects.equals(predicate, that.predicate) && + Objects.equals(object, that.object) && + Objects.equals(contexts, that.contexts); + } + + @Override + public int hashCode() { + return Objects.hash(subject, predicate, object, contexts); + } + + @Override + public String toString() { + return "EdgePattern{" + + "subject=" + subject + + ", predicate=" + predicate + + ", object=" + object + + ", contexts=" + contexts + + '}'; + } + + /** + * Builder for EdgePattern. + */ + public static final class Builder { + private Node subject; + private Node predicate; + private Node object; + private List contexts; + + private Builder() { + } + + /** + * Sets the subject constraint. + * + * @param subject Subject node (null for any) + * @return This builder (for chaining) + */ + public Builder subject(Node subject) { + this.subject = subject; + return this; + } + + /** + * Sets the predicate constraint. + * + * @param predicate Predicate node (null for any) + * @return This builder (for chaining) + */ + public Builder predicate(Node predicate) { + this.predicate = predicate; + return this; + } + + /** + * Sets the object constraint. + * + * @param object Object node (null for any) + * @return This builder (for chaining) + */ + public Builder object(Node object) { + this.object = object; + return this; + } + + /** + * Sets the contexts constraint. + * + * @param contexts List of contexts (null or empty for all) + * @return This builder (for chaining) + */ + public Builder contexts(List contexts) { + this.contexts = contexts; + return this; + } + + /** + * Sets a single context constraint. + * + * @param context Single context + * @return This builder (for chaining) + */ + public Builder context(Node context) { + this.contexts = context != null ? List.of(context) : null; + return this; + } + + /** + * Builds the EdgePattern. + * + * @return New EdgePattern instance + */ + public EdgePattern build() { + return new EdgePattern(this); + } + } +} \ No newline at end of file diff --git a/src/main/java/fr/inria/corese/core/storage/api/dataManager/support/model/GraphStatistics.java b/src/main/java/fr/inria/corese/core/storage/api/dataManager/support/model/GraphStatistics.java new file mode 100644 index 000000000..5a7717abd --- /dev/null +++ b/src/main/java/fr/inria/corese/core/storage/api/dataManager/support/model/GraphStatistics.java @@ -0,0 +1,219 @@ +package fr.inria.corese.core.storage.api.dataManager.support.model; + +import java.util.Objects; + +/** + * Statistics about a graph's content and structure. + */ +public class GraphStatistics { + + private final long edgeCount; + private final long nodeCount; + private final long predicateCount; + private final long contextCount; + private final long subjectCount; + private final long objectCount; + private final long literalCount; + + /** + * Private constructor - use Builder. + */ + private GraphStatistics(Builder builder) { + this.edgeCount = builder.edgeCount; + this.nodeCount = builder.nodeCount; + this.predicateCount = builder.predicateCount; + this.contextCount = builder.contextCount; + this.subjectCount = builder.subjectCount; + this.objectCount = builder.objectCount; + this.literalCount = builder.literalCount; + } + + /** + * Returns the total number of edges (triples) in the graph. + * + * @return Edge count + */ + public long getEdgeCount() { + return edgeCount; + } + + /** + * Returns the total number of unique nodes (subjects and objects). + * + * @return Node count + */ + public long getNodeCount() { + return nodeCount; + } + + /** + * Returns the number of unique predicates. + * + * @return Predicate count + */ + public long getPredicateCount() { + return predicateCount; + } + + /** + * Returns the number of named graphs (contexts). + * + * @return Context count + */ + public long getContextCount() { + return contextCount; + } + + + /** + * Calculates graph density: edges / (nodes * predicates). + * + * @return Graph density between 0.0 and 1.0, or 0.0 if calculation not possible + */ + public double getDensity() { + if (nodeCount == 0 || predicateCount == 0) { + return 0.0; + } + long maxPossibleEdges = nodeCount * predicateCount; + return (double) edgeCount / maxPossibleEdges; + } + + + /** + * Calculates average degree (number of edges) per node. + * + * @return Average degree per node, or 0.0 if no nodes + */ + public double getAverageDegree() { + if (nodeCount == 0) { + return 0.0; + } + // Each edge connects to 2 nodes (subject and object) + return (double) (edgeCount * 2) / nodeCount; + } + + /** + * Checks if the graph is empty. + * + * @return true if no edges + */ + public boolean isEmpty() { + return edgeCount == 0; + } + + /** + * Checks if the graph has any data. + * + * @return true if at least one edge exists + */ + public boolean hasData() { + return edgeCount > 0; + } + + @Override + public String toString() { + return String.format( + "GraphStatistics{edges=%d, nodes=%d, predicates=%d, contexts=%d, " + + "subjects=%d, objects=%d, literals=%d, density=%.4f, avgDegree=%.2f}", + edgeCount, nodeCount, predicateCount, contextCount, + subjectCount, objectCount, literalCount, + getDensity(), getAverageDegree() + ); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + GraphStatistics that = (GraphStatistics) o; + return edgeCount == that.edgeCount && + nodeCount == that.nodeCount && + predicateCount == that.predicateCount && + contextCount == that.contextCount && + subjectCount == that.subjectCount && + objectCount == that.objectCount && + literalCount == that.literalCount; + } + + @Override + public int hashCode() { + return Objects.hash(edgeCount, nodeCount, predicateCount, contextCount, + subjectCount, objectCount, literalCount); + } + + /** + * Creates a new builder. + * + * @return New builder instance + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Builder for GraphStatistics. + */ + public static class Builder { + private long edgeCount = 0; + private long nodeCount = 0; + private long predicateCount = 0; + private long contextCount = 0; + private final long subjectCount = 0; + private final long objectCount = 0; + private final long literalCount = 0; + + /** + * Sets the edge count. + * + * @param count Edge count + * @return This builder + */ + public Builder edgeCount(long count) { + this.edgeCount = Math.max(0, count); + return this; + } + + /** + * Sets the node count. + * + * @param count Node count + * @return This builder + */ + public Builder nodeCount(long count) { + this.nodeCount = Math.max(0, count); + return this; + } + + /** + * Sets the predicate count. + * + * @param count Predicate count + * @return This builder + */ + public Builder predicateCount(long count) { + this.predicateCount = Math.max(0, count); + return this; + } + + /** + * Sets the context count. + * + * @param count Context count + * @return This builder + */ + public Builder contextCount(long count) { + this.contextCount = Math.max(0, count); + return this; + } + + + /** + * Builds the GraphStatistics instance. + * + * @return New GraphStatistics instance + */ + public GraphStatistics build() { + return new GraphStatistics(this); + } + } +} \ No newline at end of file diff --git a/src/main/java/fr/inria/corese/core/storage/api/dataManager/support/model/MutationResult.java b/src/main/java/fr/inria/corese/core/storage/api/dataManager/support/model/MutationResult.java new file mode 100644 index 000000000..794f2a0fa --- /dev/null +++ b/src/main/java/fr/inria/corese/core/storage/api/dataManager/support/model/MutationResult.java @@ -0,0 +1,379 @@ +package fr.inria.corese.core.storage.api.dataManager.support.model; + +import fr.inria.corese.core.kgram.api.core.Edge; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +/** + * Unified result for mutation operations. + * + */ +public final class MutationResult { + + private final boolean isBulk; + private final boolean success; + private final List affectedEdges; + private final String message; + private final Throwable error; + + // Bulk-specific fields + private final int totalAttempted; + private final int successCount; + private final int failureCount; + private final List errors; + + /** + * Private constructor - use factory methods or Builder. + */ + private MutationResult(boolean isBulk, boolean success, List affectedEdges, + String message, Throwable error, + int totalAttempted, int successCount, int failureCount, + List errors) { + this.isBulk = isBulk; + this.success = success; + this.affectedEdges = affectedEdges != null ? + List.copyOf(affectedEdges) : + Collections.emptyList(); + this.message = message; + this.error = error; + this.totalAttempted = totalAttempted; + this.successCount = successCount; + this.failureCount = failureCount; + this.errors = errors != null ? + List.copyOf(errors) : + Collections.emptyList(); + } + + /** + * Creates a successful single-operation result. + * + * @param affectedEdge The edge that was affected (can be null for operations without edges) + * @return Successful result + */ + public static MutationResult success(Edge affectedEdge) { + return new MutationResult( + false, // not bulk + true, // success + affectedEdge != null ? List.of(affectedEdge) : Collections.emptyList(), + "Success", // message + null, // no error + 1, 1, 0, // attempted=1, success=1, failure=0 + null // no errors list + ); + } + + /** + * Creates a successful single-operation result with custom message. + * + * @param affectedEdge The edge that was affected (can be null for operations without edges) + * @param message Success message + * @return Successful result + */ + public static MutationResult success(Edge affectedEdge, String message) { + return new MutationResult( + false, true, + affectedEdge != null ? List.of(affectedEdge) : Collections.emptyList(), + message, null, + 1, 1, 0, null + ); + } + + + /** + * Creates a failure result for single operation. + * + * @param message Failure message + * @return Failed result + */ + public static MutationResult failure(String message) { + return new MutationResult( + false, false, Collections.emptyList(), message, null, + 1, 0, 1, null + ); + } + + /** + * Creates a failure result with error for single operation. + * + * @param message Failure message + * @param error The error that caused the failure + * @return Failed result + */ + public static MutationResult failure(String message, Throwable error) { + return new MutationResult( + false, false, Collections.emptyList(), message, error, + 1, 0, 1, null + ); + } + + /** + * Creates a builder for bulk operations. + * + * @return New builder instance for bulk results + */ + public static BulkBuilder bulkBuilder() { + return new BulkBuilder(); + } + + /** + * Checks if this is a bulk operation result. + * + * @return true if bulk, false if single + */ + public boolean isBulk() { + return isBulk; + } + + /** + * Checks if the operation was successful. + * For bulk: true if ALL operations succeeded. + * For single: true if the operation succeeded. + * + * @return true if successful + */ + public boolean isSuccess() { + return success; + } + + /** + * Checks if the operation failed. + * + * @return true if failed + */ + public boolean isFailure() { + return !success; + } + + + /** + * Returns the result message. + * + * @return Result message + */ + public String getMessage() { + return message; + } + + /** + * Returns the error (if any). + * + * @return Optional containing error, or empty if no error + */ + public Optional getError() { + return Optional.ofNullable(error); + } + + /** + * Returns the single affected edge. + * Convenience method for single operations. + * + * @return The affected edge, or null if none or if bulk operation + * @throws IllegalStateException if called on bulk result with multiple edges + */ + public Edge getAffectedEdge() { + if (affectedEdges.isEmpty()) { + return null; + } + if (affectedEdges.size() == 1) { + return affectedEdges.getFirst(); + } + throw new IllegalStateException( + "Multiple edges affected (" + affectedEdges.size() + + "). Use getAffectedEdges() for bulk operations." + ); + } + + /** + * Returns the total number of operations attempted. + * For single operations: always 1. + * + * @return Total attempted + */ + public int getTotalAttempted() { + return totalAttempted; + } + + /** + * Returns the number of successful operations. + * For single operations: 1 if success, 0 if failure. + * + * @return Success count + */ + public int getSuccessCount() { + return successCount; + } + + /** + * Returns the number of failed operations. + * For single operations: 0 if success, 1 if failure. + * + * @return Failure count + */ + public int getFailureCount() { + return failureCount; + } + + /** + * Returns all errors that occurred (bulk operations only). + * For single operations: empty list (use getError() instead). + * + * @return Unmodifiable list of errors + */ + public List getErrors() { + return errors; + } + + /** + * Checks if all operations were successful (bulk). + * For single operations: same as isSuccess(). + * + * @return true if all successful + */ + public boolean isCompleteSuccess() { + return failureCount == 0 && successCount == totalAttempted; + } + + + /** + * Returns the success rate (0.0 to 1.0). + * + * @return Success rate + */ + public double getSuccessRate() { + if (totalAttempted == 0) { + return 0.0; + } + return (double) successCount / totalAttempted; + } + + + @Override + public String toString() { + if (isBulk) { + return "MutationResult{bulk=true, attempted=" + totalAttempted + + ", success=" + successCount + ", failure=" + failureCount + + ", successRate=" + String.format("%.2f%%", getSuccessRate() * 100) + "}"; + } else { + if (success) { + return "MutationResult{success=true, edge=" + getAffectedEdge() + + ", message='" + message + "'}"; + } else { + return "MutationResult{success=false, message='" + message + "'" + + (error != null ? ", error=" + error.getClass().getSimpleName() : "") + "}"; + } + } + } + + /** + * Represents an error in a bulk operation. + */ + public static class MutationError { + private final Edge edge; + private final String message; + private final Throwable cause; + + public MutationError(Edge edge, String message) { + this(edge, message, null); + } + + public MutationError(Edge edge, String message, Throwable cause) { + this.edge = edge; + this.message = message; + this.cause = cause; + } + + public Edge getEdge() { + return edge; + } + + public String getMessage() { + return message; + } + + public Throwable getCause() { + return cause; + } + + @Override + public String toString() { + return "MutationError{edge=" + edge + ", message='" + message + "'}"; + } + } + + /** + * Builder for bulk operation results. + */ + public static class BulkBuilder { + private int totalAttempted = 0; + private int successCount = 0; + private int failureCount = 0; + private final List affectedEdges = new ArrayList<>(); + private final List errors = new ArrayList<>(); + private String message = "Bulk operation completed"; + + private BulkBuilder() { + } + + public BulkBuilder totalAttempted(int total) { + this.totalAttempted = total; + return this; + } + + public BulkBuilder message(String message) { + this.message = message; + return this; + } + + public BulkBuilder incrementSuccess() { + this.successCount++; + return this; + } + + public BulkBuilder incrementFailure() { + this.failureCount++; + return this; + } + + public void addSuccess(Edge edge) { + if (edge != null) { + this.affectedEdges.add(edge); + } + this.successCount++; + } + + public void addFailure(Edge edge, String errorMessage) { + this.errors.add(new MutationError(edge, errorMessage)); + this.failureCount++; + } + + public void addFailure(Edge edge, String errorMessage, Throwable cause) { + this.errors.add(new MutationError(edge, errorMessage, cause)); + this.failureCount++; + } + + public BulkBuilder addAffectedEdges(List edges) { + if (edges != null) { + this.affectedEdges.addAll(edges); + } + return this; + } + + public MutationResult build() { + boolean success = failureCount == 0 && successCount == totalAttempted; + return new MutationResult( + true, // is bulk + success, // overall success + affectedEdges, // affected edges + message, // message + null, // no single error + totalAttempted, + successCount, + failureCount, + errors + ); + } + } +} \ No newline at end of file diff --git a/src/main/java/fr/inria/corese/core/storage/api/dataManager/transaction/IsolationLevel.java b/src/main/java/fr/inria/corese/core/storage/api/dataManager/transaction/IsolationLevel.java new file mode 100644 index 000000000..8296c30b3 --- /dev/null +++ b/src/main/java/fr/inria/corese/core/storage/api/dataManager/transaction/IsolationLevel.java @@ -0,0 +1,48 @@ +package fr.inria.corese.core.storage.api.dataManager.transaction; + +/** + * Transaction isolation levels. + */ +public enum IsolationLevel { + + /** + * Read Uncommitted. + */ + READ_UNCOMMITTED(1), + + /** + * Read Committed. + */ + READ_COMMITTED(2), + + /** + * Repeatable Read. + * Guarantees that if a transaction reads data multiple times, + * it will always get the same value. + */ + REPEATABLE_READ(3), + + /** + * Serializable. + * Strictest isolation level. + * Transactions execute as if they were sequential. + */ + SERIALIZABLE(4); + + private final int level; + + IsolationLevel(int level) { + this.level = level; + } + + /** + * Returns the numeric isolation level. + * Higher number means stricter isolation. + * + * @return Numeric level + */ + public int getLevel() { + return level; + } + +} \ No newline at end of file diff --git a/src/main/java/fr/inria/corese/core/storage/api/dataManager/transaction/Transaction.java b/src/main/java/fr/inria/corese/core/storage/api/dataManager/transaction/Transaction.java new file mode 100644 index 000000000..cc3b10774 --- /dev/null +++ b/src/main/java/fr/inria/corese/core/storage/api/dataManager/transaction/Transaction.java @@ -0,0 +1,69 @@ +package fr.inria.corese.core.storage.api.dataManager.transaction; + +import fr.inria.corese.core.storage.api.dataManager.support.exception.DataManagerException; + +/** + * Handle representing an active transaction. + */ +public interface Transaction extends AutoCloseable { + + /** + * Returns the unique identifier of this transaction. + * + * @return Transaction unique ID + */ + String getId(); + + /** + * Returns the isolation level of this transaction. + * + * @return Isolation level + */ + IsolationLevel getIsolationLevel(); + + + /** + * Commits (validates) the transaction. + * All modifications made in this transaction become permanent. + * + * @throws DataManagerException if commit fails + * @throws IllegalStateException if transaction is no longer active + */ + void commit() throws DataManagerException; + + /** + * Rolls back (cancels) the transaction. + * All modifications made in this transaction are cancelled. + * + * @throws DataManagerException if rollback fails + * @throws IllegalStateException if transaction is no longer active + */ + void rollback() throws DataManagerException; + + /** + * Checks if the transaction is still active. + * A transaction is active if it has been created but not yet committed or rolled back. + * + * @return true if transaction is active, false otherwise + */ + boolean isActive(); + + /** + * Returns the current transaction state. + * + * @return Transaction state + */ + TransactionState getState(); + + + /** + * Closes the transaction. + * If transaction is still active, performs automatic rollback. + * + * @throws DataManagerException if close fails + */ + @Override + void close() throws DataManagerException; + + +} \ No newline at end of file diff --git a/src/main/java/fr/inria/corese/core/storage/api/dataManager/transaction/TransactionManager.java b/src/main/java/fr/inria/corese/core/storage/api/dataManager/transaction/TransactionManager.java new file mode 100644 index 000000000..87f2e6323 --- /dev/null +++ b/src/main/java/fr/inria/corese/core/storage/api/dataManager/transaction/TransactionManager.java @@ -0,0 +1,69 @@ +package fr.inria.corese.core.storage.api.dataManager.transaction; + +import fr.inria.corese.core.storage.api.dataManager.support.exception.DataManagerException; + +import java.util.Optional; +import java.util.Set; + +/** + * Transaction management for the DataManager. + */ +public interface TransactionManager { + + /** + * Checks if this DataManager supports transactions. + * + * @return true if transactions are supported, false otherwise + */ + boolean supportsTransactions(); + + /** + * Starts a new transaction with the default isolation level. + * + * @return Created transaction handle + * @throws DataManagerException if transaction cannot be started + * @throws UnsupportedOperationException if transactions are not supported + */ + Transaction beginTransaction() throws DataManagerException; + + /** + * Starts a new transaction with a specific isolation level. + * + * @param isolationLevel Desired isolation level + * @return Created transaction handle + * @throws DataManagerException if transaction cannot be started + * @throws UnsupportedOperationException if transactions are not supported + * @throws IllegalArgumentException if isolation level is not supported + */ + Transaction beginTransaction(IsolationLevel isolationLevel) throws DataManagerException; + + /** + * Gets the current transaction of the current thread. + * + * @return Current transaction, or Optional.empty() if no active transaction + */ + Optional getCurrentTransaction(); + + /** + * Checks if a transaction is active on the current thread. + * + * @return true if a transaction is active + */ + default boolean hasActiveTransaction() { + return getCurrentTransaction().map(Transaction::isActive).orElse(false); + } + + /** + * Returns the default isolation level used for new transactions. + * + * @return Default isolation level + */ + IsolationLevel getDefaultIsolationLevel(); + + /** + * Returns the isolation levels supported by this manager. + * + * @return Set of supported isolation levels + */ + Set getSupportedIsolationLevels(); +} \ No newline at end of file diff --git a/src/main/java/fr/inria/corese/core/storage/api/dataManager/transaction/TransactionState.java b/src/main/java/fr/inria/corese/core/storage/api/dataManager/transaction/TransactionState.java new file mode 100644 index 000000000..de2bbda4b --- /dev/null +++ b/src/main/java/fr/inria/corese/core/storage/api/dataManager/transaction/TransactionState.java @@ -0,0 +1,42 @@ +package fr.inria.corese.core.storage.api.dataManager.transaction; + +public enum TransactionState { + /** + * Transaction is active + */ + ACTIVE("Active"), + + /** + * Transaction has been committed + */ + COMMITTED("Committed"), + + /** + * Transaction has been rolled back + */ + ROLLED_BACK("Rolled back"), + + /** + * Transaction is in error + */ + FAILED("Failed"); + + private final String description; + + TransactionState(String description) { + this.description = description; + } + + public String getDescription() { + return description; + } + + /** + * Checks if transaction can still be used. + * + * @return true if active + */ + public boolean isActive() { + return this == ACTIVE; + } +} \ No newline at end of file diff --git a/src/main/java/fr/inria/corese/core/storage/impl/lifecycle/LifecycleManagerImpl.java b/src/main/java/fr/inria/corese/core/storage/impl/lifecycle/LifecycleManagerImpl.java new file mode 100644 index 000000000..3c5bdffca --- /dev/null +++ b/src/main/java/fr/inria/corese/core/storage/impl/lifecycle/LifecycleManagerImpl.java @@ -0,0 +1,169 @@ +package fr.inria.corese.core.storage.impl.lifecycle; + +import fr.inria.corese.core.Graph; +import fr.inria.corese.core.storage.api.dataManager.lifecycle.DataManagerLifecycle; +import fr.inria.corese.core.storage.api.dataManager.lifecycle.LifecycleState; +import fr.inria.corese.core.storage.api.dataManager.support.config.DataManagerConfig; +import fr.inria.corese.core.storage.api.dataManager.support.exception.DataManagerException; +import fr.inria.corese.core.storage.api.dataManager.support.exception.ErrorCode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Lifecycle manager implementation for CoreseGraphDataManager. + * Manages initialization, state and shutdown of the DataManager. + */ +public class LifecycleManagerImpl implements DataManagerLifecycle { + + private static final Logger logger = LoggerFactory.getLogger(LifecycleManagerImpl.class); + + // Reference to the graph to manage + private final Graph graph; + + // Current lifecycle state (volatile for thread-safety) + private volatile LifecycleState state; + + // Current configuration + private DataManagerConfig config; + + // Lock for state change synchronization + private final Object stateLock = new Object(); + + /** + * Constructs a lifecycle manager for a graph. + * + * @param graph Corese Graph to manage + * @throws IllegalArgumentException if graph is null + */ + public LifecycleManagerImpl(Graph graph) { + if (graph == null) { + throw new IllegalArgumentException("Graph cannot be null"); + } + this.graph = graph; + this.state = LifecycleState.NOT_INITIALIZED; + } + + @Override + public void initialize(DataManagerConfig config) throws DataManagerException { + if (config == null) { + throw new IllegalArgumentException("Config cannot be null"); + } + + synchronized (stateLock) { + // State verification + if (state == LifecycleState.RUNNING) { + throw new IllegalStateException("Already initialized"); + } + if (state == LifecycleState.INITIALIZING) { + throw new IllegalStateException("Already initializing"); + } + + logger.info("Initializing DataManager with config: {}", config); + state = LifecycleState.INITIALIZING; + + try { + // Save configuration + this.config = config; + + // Initialize the graph + graph.init(); + + // Apply specific configurations + applyConfiguration(config); + + // Move to RUNNING state + state = LifecycleState.RUNNING; + logger.info("DataManager initialized successfully"); + + } catch (Exception e) { + // Restore state on error + state = LifecycleState.NOT_INITIALIZED; + this.config = null; + + logger.error("Failed to initialize DataManager", e); + throw new DataManagerException( + ErrorCode.INITIALIZATION_FAILED, + "Failed to initialize DataManager: " + e.getMessage(), + e + ); + } + } + } + + @Override + public boolean isInitialized() { + return state == LifecycleState.RUNNING; + } + + @Override + public void shutdown() throws DataManagerException { + synchronized (stateLock) { + if (state != LifecycleState.RUNNING) { + throw new IllegalStateException("Not running, current state: " + state); + } + + logger.info("Shutting down DataManager"); + state = LifecycleState.SHUTTING_DOWN; + + try { + // Clean up resources + graph.init(); + + // Move to SHUTDOWN state + state = LifecycleState.SHUTDOWN; + logger.info("DataManager shut down successfully"); + + } catch (Exception e) { + // On error, stay in SHUTTING_DOWN + logger.error("Failed to shutdown DataManager cleanly", e); + throw new DataManagerException( + ErrorCode.SHUTDOWN_FAILED, + "Failed to shutdown DataManager: " + e.getMessage(), + e + ); + } + } + } + + @Override + public LifecycleState getState() { + return state; + } + + @Override + public DataManagerConfig getConfig() { + return config; + } + + /** + * Applies the configuration to the graph. + * This method can be extended to support more options. + * + * @param config Configuration to apply + */ + private void applyConfiguration(DataManagerConfig config) { + if (config.isDebug()) { + logger.debug("Debug mode enabled"); + } + + // Storage path (for information) + logger.info("Storage path: {}", config.getStoragePath()); + + // Other configurations can be added here + config.getProperties().forEach((key, value) -> logger.debug("Config property: {} = {}", key, value)); + } + + /** + * Checks that the DataManager is in a usable state. + * Throws an exception if not. + * + * @throws IllegalStateException if not in RUNNING state + */ + public void checkUsable() { + if (!state.isUsable()) { + throw new IllegalStateException( + "DataManager is not usable, current state: " + state + ); + } + } +} \ No newline at end of file diff --git a/src/main/java/fr/inria/corese/core/storage/impl/operations/BulkOperationsImpl.java b/src/main/java/fr/inria/corese/core/storage/impl/operations/BulkOperationsImpl.java new file mode 100644 index 000000000..dc64265c6 --- /dev/null +++ b/src/main/java/fr/inria/corese/core/storage/impl/operations/BulkOperationsImpl.java @@ -0,0 +1,422 @@ +package fr.inria.corese.core.storage.impl.operations; + +import fr.inria.corese.core.Graph; +import fr.inria.corese.core.kgram.api.core.Edge; +import fr.inria.corese.core.kgram.api.core.Node; +import fr.inria.corese.core.storage.api.dataManager.operations.BulkOperations; +import fr.inria.corese.core.storage.api.dataManager.support.exception.DataManagerException; +import fr.inria.corese.core.storage.api.dataManager.support.exception.ErrorCode; +import fr.inria.corese.core.storage.api.dataManager.support.model.EdgePattern; +import fr.inria.corese.core.storage.api.dataManager.support.model.MutationResult; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.List; + +/** + * Implementation of bulk operations for CoreseGraphDataManager. + */ +public class BulkOperationsImpl implements BulkOperations { + + private static final Logger logger = LoggerFactory.getLogger(BulkOperationsImpl.class); + + private final Graph graph; + + /** + * Constructs bulk operations for a graph. + * + * @param graph Graph to operate on + * @throws IllegalArgumentException if graph is null + */ + public BulkOperationsImpl(Graph graph) { + if (graph == null) { + throw new IllegalArgumentException("Graph cannot be null"); + } + this.graph = graph; + } + + @Override + public MutationResult insertBatch(List edges) throws DataManagerException { + if (edges == null || edges.isEmpty()) { + throw new IllegalArgumentException("Edges list cannot be null or empty"); + } + + logger.info("Inserting batch of {} edges", edges.size()); + + MutationResult.BulkBuilder builder = MutationResult.bulkBuilder() + .totalAttempted(edges.size()) + .message("Batch insert of " + edges.size() + " edge(s)"); + + try { + for (Edge edge : edges) { + try { + Edge inserted = graph.insertEdgeWithTargetNode(edge); + if (inserted != null) { + builder.addSuccess(inserted); + } else { + builder.addFailure(edge, "Insert returned null"); + } + } catch (Exception e) { + builder.addFailure(edge, "Insert failed: " + e.getMessage(), e); + } + } + + MutationResult result = builder.build(); + logger.info("Batch insert completed: success={}, failure={}", + result.getSuccessCount(), result.getFailureCount()); + return result; + + } catch (Exception e) { + logger.error("Batch insert failed", e); + throw new DataManagerException( + ErrorCode.MUTATION_FAILED, + "Batch insert failed: " + e.getMessage(), + e + ); + } + } + + @Override + public MutationResult deleteBatch(List edges) throws DataManagerException { + if (edges == null || edges.isEmpty()) { + throw new IllegalArgumentException("Edges list cannot be null or empty"); + } + + logger.info("Deleting batch of {} edges", edges.size()); + + MutationResult.BulkBuilder builder = MutationResult.bulkBuilder() + .totalAttempted(edges.size()) + .message("Batch delete of " + edges.size() + " edge(s)"); + + try { + for (Edge edge : edges) { + try { + Iterable deleted = graph.deleteEdgeWithTargetNode(edge); + + boolean foundAny = false; + if (deleted != null) { + for (Edge e : deleted) { + if (e != null) { + builder.addSuccess(e); + foundAny = true; + } + } + } + + if (!foundAny) { + builder.addFailure(edge, "Edge not found"); + } + } catch (Exception e) { + builder.addFailure(edge, "Delete failed: " + e.getMessage(), e); + } + } + + MutationResult result = builder.build(); + logger.info("Batch delete completed: success={}, failure={}", + result.getSuccessCount(), result.getFailureCount()); + return result; + + } catch (Exception e) { + logger.error("Batch delete failed", e); + throw new DataManagerException( + ErrorCode.MUTATION_FAILED, + "Batch delete failed: " + e.getMessage(), + e + ); + } + } + + @Override + public MutationResult deleteByPattern(EdgePattern pattern) throws DataManagerException { + if (pattern == null) { + throw new IllegalArgumentException("Pattern cannot be null"); + } + + logger.info("Deleting by pattern: {}", pattern); + + try { + Node subject = pattern.getSubject(); + Node predicate = pattern.getPredicate(); + Node object = pattern.getObject(); + List contexts = pattern.getContexts(); + + // Use Graph's delete method + Iterable deleted = graph.delete(subject, predicate, object, contexts); + + // Convert to list and count + List deletedList = new ArrayList<>(); + if (deleted != null) { + for (Edge e : deleted) { + if (e != null) { + deletedList.add(e); + } + } + } + + logger.info("Deleted {} edge(s) by pattern", deletedList.size()); + + // Build result + MutationResult.BulkBuilder builder = MutationResult.bulkBuilder() + .totalAttempted(deletedList.size()) + .addAffectedEdges(deletedList) + .message("Deleted " + deletedList.size() + " edge(s) by pattern"); + + for (int i = 0; i < deletedList.size(); i++) { + builder.incrementSuccess(); + } + + return builder.build(); + + } catch (Exception e) { + logger.error("Delete by pattern failed", e); + throw new DataManagerException( + ErrorCode.MUTATION_FAILED, + "Delete by pattern failed: " + e.getMessage(), + e + ); + } + } + + @Override + public MutationResult clearContexts(List contexts, boolean silent) + throws DataManagerException { + + if (contexts == null || contexts.isEmpty()) { + return clearAll(); + } + + logger.info("Clearing {} context(s), silent={}", contexts.size(), silent); + + MutationResult.BulkBuilder builder = MutationResult.bulkBuilder() + .totalAttempted(contexts.size()) + .message("Cleared " + contexts.size() + " context(s)"); + + try { + int totalDeleted = 0; + + for (Node context : contexts) { + try { + // Use Graph's clear method + graph.clear(context.getLabel(), silent); + builder.incrementSuccess(); + totalDeleted++; + } catch (Exception e) { + if (!silent) { + builder.addFailure(null, "Failed to clear context " + context + ": " + e.getMessage(), e); + } else { + builder.incrementSuccess(); // Silent mode = consider as success + } + } + } + + logger.info("Cleared {} context(s)", totalDeleted); + return builder.build(); + + } catch (Exception e) { + logger.error("Clear contexts failed", e); + throw new DataManagerException( + ErrorCode.MUTATION_FAILED, + "Clear contexts failed: " + e.getMessage(), + e + ); + } + } + + @Override + public MutationResult clearAll() throws DataManagerException { + logger.warn("Clearing ALL data from graph"); + + try { + int sizeBefore = graph.size(); + + // Clear all data + graph.clear(); + graph.dropGraphNames(); + + logger.info("Cleared {} edge(s) and all graph names", sizeBefore); + + return MutationResult.bulkBuilder() + .totalAttempted(sizeBefore) + .incrementSuccess() // Consider as single successful operation + .message("Cleared all data (" + sizeBefore + " edge(s))") + .build(); + + } catch (Exception e) { + logger.error("Clear all failed", e); + throw new DataManagerException( + ErrorCode.MUTATION_FAILED, + "Clear all failed: " + e.getMessage(), + e + ); + } + } + + @Override + public MutationResult addGraph(Node sourceContext, Node targetContext, boolean silent) + throws DataManagerException { + + if (sourceContext == null || targetContext == null) { + throw new IllegalArgumentException("Source and target contexts cannot be null"); + } + + logger.info("Adding graph from {} to {}, silent={}", sourceContext, targetContext, silent); + + try { + boolean success = graph.add( + sourceContext.getLabel(), + targetContext.getLabel(), + silent + ); + + if (success) { + return MutationResult.bulkBuilder() + .totalAttempted(1) + .incrementSuccess() + .message("Added graph from " + sourceContext + " to " + targetContext) + .build(); + } else { + return MutationResult.bulkBuilder() + .totalAttempted(1) + .incrementFailure() + .message("Failed to add graph") + .build(); + } + + } catch (Exception e) { + logger.error("Add graph failed", e); + throw new DataManagerException( + ErrorCode.MUTATION_FAILED, + "Add graph failed: " + e.getMessage(), + e + ); + } + } + + @Override + public MutationResult copyGraph(Node sourceContext, Node targetContext, boolean silent) + throws DataManagerException { + + if (sourceContext == null || targetContext == null) { + throw new IllegalArgumentException("Source and target contexts cannot be null"); + } + + logger.info("Copying graph from {} to {}, silent={}", sourceContext, targetContext, silent); + + try { + boolean success = graph.copy( + sourceContext.getLabel(), + targetContext.getLabel(), + silent + ); + + if (success) { + return MutationResult.bulkBuilder() + .totalAttempted(1) + .incrementSuccess() + .message("Copied graph from " + sourceContext + " to " + targetContext) + .build(); + } else { + return MutationResult.bulkBuilder() + .totalAttempted(1) + .incrementFailure() + .message("Failed to copy graph") + .build(); + } + + } catch (Exception e) { + logger.error("Copy graph failed", e); + throw new DataManagerException( + ErrorCode.MUTATION_FAILED, + "Copy graph failed: " + e.getMessage(), + e + ); + } + } + + @Override + public MutationResult moveGraph(Node sourceContext, Node targetContext, boolean silent) + throws DataManagerException { + + if (sourceContext == null || targetContext == null) { + throw new IllegalArgumentException("Source and target contexts cannot be null"); + } + + logger.info("Moving graph from {} to {}, silent={}", sourceContext, targetContext, silent); + + try { + boolean success = graph.move( + sourceContext.getLabel(), + targetContext.getLabel(), + silent + ); + + if (success) { + return MutationResult.bulkBuilder() + .totalAttempted(1) + .incrementSuccess() + .message("Moved graph from " + sourceContext + " to " + targetContext) + .build(); + } else { + return MutationResult.bulkBuilder() + .totalAttempted(1) + .incrementFailure() + .message("Failed to move graph") + .build(); + } + + } catch (Exception e) { + logger.error("Move graph failed", e); + throw new DataManagerException( + ErrorCode.MUTATION_FAILED, + "Move graph failed: " + e.getMessage(), + e + ); + } + } + + @Override + public MutationResult declareContext(Node context) throws DataManagerException { + if (context == null) { + throw new IllegalArgumentException("Context cannot be null"); + } + + logger.debug("Declaring context: {}", context); + + try { + graph.addGraphNode(context); + + return MutationResult.success(null, "Context declared: " + context); + + } catch (Exception e) { + logger.error("Declare context failed", e); + throw new DataManagerException( + ErrorCode.MUTATION_FAILED, + "Declare context failed: " + e.getMessage(), + e + ); + } + } + + @Override + public MutationResult undeclareContext(Node context) throws DataManagerException { + if (context == null) { + throw new IllegalArgumentException("Context cannot be null"); + } + + logger.info("Undeclaring context: {}", context); + + try { + // Clear the context (deletes all edges) + return clearContexts(List.of(context), false); + + } catch (Exception e) { + logger.error("Undeclare context failed", e); + throw new DataManagerException( + ErrorCode.MUTATION_FAILED, + "Undeclare context failed: " + e.getMessage(), + e + ); + } + } +} \ No newline at end of file diff --git a/src/main/java/fr/inria/corese/core/storage/impl/operations/MetadataOperationsImpl.java b/src/main/java/fr/inria/corese/core/storage/impl/operations/MetadataOperationsImpl.java new file mode 100644 index 000000000..35bd638f2 --- /dev/null +++ b/src/main/java/fr/inria/corese/core/storage/impl/operations/MetadataOperationsImpl.java @@ -0,0 +1,170 @@ +package fr.inria.corese.core.storage.impl.operations; + +import fr.inria.corese.core.Graph; +import fr.inria.corese.core.kgram.api.core.Node; +import fr.inria.corese.core.storage.api.dataManager.operations.MetadataOperations; +import fr.inria.corese.core.storage.api.dataManager.support.exception.DataManagerException; +import fr.inria.corese.core.storage.api.dataManager.support.exception.ErrorCode; +import fr.inria.corese.core.storage.api.dataManager.support.model.GraphStatistics; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +/** + * Implementation of metadata operations for CoreseGraphDataManager. + * Converts Graph's Iterable-based API to Set-based API. + */ +public class MetadataOperationsImpl implements MetadataOperations { + + private static final Logger logger = LoggerFactory.getLogger(MetadataOperationsImpl.class); + + private final Graph graph; + + /** + * Constructs metadata operations for a graph. + * + * @param graph Graph to query + * @throws IllegalArgumentException if graph is null + */ + public MetadataOperationsImpl(Graph graph) { + if (graph == null) { + throw new IllegalArgumentException("Graph cannot be null"); + } + this.graph = graph; + } + + @Override + public Set getPredicates(Node context) throws DataManagerException { + try { + logger.debug("Getting predicates for context: {}", context); + + // Graph.getSortedProperties() returns all predicates + // Note: context parameter is not used by Graph implementation + Iterable iterable = graph.getSortedProperties(); + + // Convert to Set + Set predicates = iterableToSet(iterable); + + logger.debug("Found {} predicates", predicates.size()); + return Collections.unmodifiableSet(predicates); + + } catch (Exception e) { + logger.error("Failed to get predicates for context: {}", context, e); + throw new DataManagerException( + ErrorCode.QUERY_FAILED, + "Failed to get predicates: " + e.getMessage(), + e + ); + } + } + + @Override + public Set getNodes(Node context) throws DataManagerException { + try { + logger.debug("Getting nodes for context: {}", context); + + Iterable iterable; + if (context == null) { + // All nodes + iterable = graph.getNodeGraphIterator(); + } else { + // Nodes in specific context + Node graphNode = graph.getNode(context); + iterable = graph.getNodeGraphIterator(graphNode); + } + + // Convert to Set + Set nodes = iterableToSet(iterable); + + logger.debug("Found {} nodes", nodes.size()); + return Collections.unmodifiableSet(nodes); + + } catch (Exception e) { + logger.error("Failed to get nodes for context: {}", context, e); + throw new DataManagerException( + ErrorCode.QUERY_FAILED, + "Failed to get nodes: " + e.getMessage(), + e + ); + } + } + + @Override + public Set getContexts() throws DataManagerException { + try { + logger.debug("Getting all contexts"); + + // Graph.getGraphNodes() returns all named graphs + Iterable iterable = graph.getGraphNodes(new ArrayList<>(0)); + + // Convert to Set + Set contexts = iterableToSet(iterable); + + logger.debug("Found {} contexts", contexts.size()); + return Collections.unmodifiableSet(contexts); + + } catch (Exception e) { + logger.error("Failed to get contexts", e); + throw new DataManagerException( + ErrorCode.QUERY_FAILED, + "Failed to get contexts: " + e.getMessage(), + e + ); + } + } + + @Override + public GraphStatistics getStatistics() throws DataManagerException { + try { + logger.debug("Collecting graph statistics"); + + // Collect statistics + long edgeCount = graph.size(); + long nodeCount = getNodes(null).size(); + long predicateCount = getPredicates(null).size(); + long contextCount = getContexts().size(); + + // Build GraphStatistics with Builder pattern + GraphStatistics stats = GraphStatistics.builder() + .edgeCount(edgeCount) + .nodeCount(nodeCount) + .predicateCount(predicateCount) + .contextCount(contextCount) + .build(); + + logger.debug("Statistics: {}", stats); + return stats; + + } catch (Exception e) { + logger.error("Failed to collect statistics", e); + throw new DataManagerException( + ErrorCode.QUERY_FAILED, + "Failed to collect statistics: " + e.getMessage(), + e + ); + } + } + + /** + * Converts an Iterable to a Set. + * Helper method to convert Graph's Iterable results to Sets. + * + * @param iterable Iterable to convert + * @return Set containing all elements + */ + private Set iterableToSet(Iterable iterable) { + Set set = new HashSet<>(); + if (iterable != null) { + for (Node node : iterable) { + if (node != null) { + set.add(node); + } + } + } + return set; + } +} \ No newline at end of file diff --git a/src/main/java/fr/inria/corese/core/storage/impl/operations/MutationOperationsImpl.java b/src/main/java/fr/inria/corese/core/storage/impl/operations/MutationOperationsImpl.java new file mode 100644 index 000000000..efba36911 --- /dev/null +++ b/src/main/java/fr/inria/corese/core/storage/impl/operations/MutationOperationsImpl.java @@ -0,0 +1,253 @@ +package fr.inria.corese.core.storage.impl.operations; + +import fr.inria.corese.core.Graph; +import fr.inria.corese.core.kgram.api.core.Edge; +import fr.inria.corese.core.kgram.api.core.Node; +import fr.inria.corese.core.storage.api.dataManager.operations.MutationOperations; +import fr.inria.corese.core.storage.api.dataManager.support.exception.DataManagerException; +import fr.inria.corese.core.storage.api.dataManager.support.exception.ErrorCode; +import fr.inria.corese.core.storage.api.dataManager.support.model.MutationResult; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.List; + +/** + * Implementation of mutation operations for CoreseGraphDataManager. + */ +public class MutationOperationsImpl implements MutationOperations { + + private static final Logger logger = LoggerFactory.getLogger(MutationOperationsImpl.class); + + private final Graph graph; + + /** + * Constructs mutation operations for a graph. + * + * @param graph Graph to mutate + * @throws IllegalArgumentException if graph is null + */ + public MutationOperationsImpl(Graph graph) { + if (graph == null) { + throw new IllegalArgumentException("Graph cannot be null"); + } + this.graph = graph; + } + + @Override + public MutationResult insertEdge(Edge edge) throws DataManagerException { + if (edge == null) { + throw new IllegalArgumentException("Edge cannot be null"); + } + + try { + logger.debug("Inserting edge: {}", edge); + + // Use Graph's insertEdgeWithTargetNode + Edge inserted = graph.insertEdgeWithTargetNode(edge); + + if (inserted != null) { + logger.debug("Edge inserted successfully"); + return MutationResult.success(inserted, "Edge inserted"); + } else { + logger.warn("Edge insertion returned null"); + return MutationResult.failure("Edge insertion failed"); + } + + } catch (Exception e) { + logger.error("Failed to insert edge: {}", edge, e); + throw new DataManagerException( + ErrorCode.MUTATION_FAILED, + "Failed to insert edge: " + e.getMessage(), + e + ); + } + } + + @Override + public MutationResult insertEdge(Node subject, Node predicate, Node object, List contexts) + throws DataManagerException { + + if (subject == null || predicate == null || object == null) { + throw new IllegalArgumentException("Subject, predicate, and object cannot be null"); + } + + try { + logger.debug("Inserting edge: ({}, {}, {}) in contexts: {}", + subject, predicate, object, contexts); + + // Track size before insert + int sizeBefore = graph.size(); + + // Use Graph's insert method + Iterable inserted = graph.insert(subject, predicate, object, contexts); + + // Convert Iterable to List + List insertedList = new ArrayList<>(); + if (inserted != null) { + for (Edge e : inserted) { + if (e != null) { + insertedList.add(e); + } + } + } + + // Check if insertion actually happened + int sizeAfter = graph.size(); + boolean insertionOccurred = (sizeAfter > sizeBefore) || !insertedList.isEmpty(); + + if (insertionOccurred) { + logger.debug("Inserted edge successfully"); + + // If we got edges back, use them + if (!insertedList.isEmpty()) { + if (insertedList.size() == 1) { + return MutationResult.success(insertedList.getFirst(), "Edge inserted"); + } + + // Multiple edges (multiple contexts) + MutationResult.BulkBuilder builder = MutationResult.bulkBuilder() + .totalAttempted(insertedList.size()) + .addAffectedEdges(insertedList) + .message("Inserted " + insertedList.size() + " edge(s)"); + + for (int i = 0; i < insertedList.size(); i++) { + builder.incrementSuccess(); + } + + return builder.build(); + } else { + // Insertion happened but no edges returned - create edge reference + Edge edge = graph.create( + contexts != null && !contexts.isEmpty() ? contexts.getFirst() : graph.getDefaultGraphNode(), + subject, predicate, object + ); + return MutationResult.success(edge, "Edge inserted (verified by size change)"); + } + } else { + logger.warn("Edge insertion did not change graph size (possible duplicate)"); + // May be a duplicate - still return success with note + Edge edge = graph.create( + contexts != null && !contexts.isEmpty() ? contexts.getFirst() : graph.getDefaultGraphNode(), + subject, predicate, object + ); + return MutationResult.success(edge, "Edge may already exist"); + } + + } catch (Exception e) { + logger.error("Failed to insert edge: ({}, {}, {})", subject, predicate, object, e); + throw new DataManagerException( + ErrorCode.MUTATION_FAILED, + "Failed to insert edge: " + e.getMessage(), + e + ); + } + } + + @Override + public MutationResult deleteEdge(Edge edge) throws DataManagerException { + if (edge == null) { + throw new IllegalArgumentException("Edge cannot be null"); + } + + try { + logger.debug("Deleting edge: {}", edge); + + // Use Graph's deleteEdgeWithTargetNode + Iterable deleted = graph.deleteEdgeWithTargetNode(edge); + + // Convert to list + List deletedList = new ArrayList<>(); + if (deleted != null) { + for (Edge e : deleted) { + if (e != null) { + deletedList.add(e); + } + } + } + + if (!deletedList.isEmpty()) { + // Return first deleted edge (typically only one) + return MutationResult.success(deletedList.getFirst(), "Edge deleted"); + } else { + logger.warn("Edge deletion returned empty (edge may not exist)"); + return MutationResult.failure("Edge not found or deletion failed"); + } + + } catch (Exception e) { + logger.error("Failed to delete edge: {}", edge, e); + throw new DataManagerException( + ErrorCode.MUTATION_FAILED, + "Failed to delete edge: " + e.getMessage(), + e + ); + } + } + + @Override + public MutationResult deleteEdges(Node subject, Node predicate, Node object, List contexts) + throws DataManagerException { + + try { + logger.debug("Deleting edges: ({}, {}, {}) in contexts: {}", + subject, predicate, object, contexts); + + // Use graph.iterate() to find matching edges (supports null wildcards) + Iterable matchingEdges = graph.iterate(subject, predicate, object, contexts); + + // Delete each matching edge + List deletedList = new ArrayList<>(); + for (Edge edge : matchingEdges) { + if (edge != null) { + List deleted = graph.delete(edge); + if (deleted != null) { + for (Edge e : deleted) { + if (e != null) { + deletedList.add(e); + } + } + } + } + } + + logger.debug("Deleted {} edge(s)", deletedList.size()); + + // Build bulk result + MutationResult.BulkBuilder builder = MutationResult.bulkBuilder() + .totalAttempted(deletedList.size()) + .addAffectedEdges(deletedList) + .message("Deleted " + deletedList.size() + " edge(s)"); + + for (int i = 0; i < deletedList.size(); i++) { + builder.incrementSuccess(); + } + + return builder.build(); + + } catch (Exception e) { + logger.error("Failed to delete edges: ({}, {}, {})", subject, predicate, object, e); + throw new DataManagerException( + ErrorCode.MUTATION_FAILED, + "Failed to delete edges: " + e.getMessage(), + e + ); + } + } + + @Override + public String generateBlankNode() throws DataManagerException { + try { + String blankId = graph.newBlankID(); + logger.debug("Generated blank node: {}", blankId); + return blankId; + } catch (Exception e) { + logger.error("Failed to generate blank node", e); + throw new DataManagerException( + ErrorCode.MUTATION_FAILED, + "Failed to generate blank node: " + e.getMessage(), + e + ); + } + } +} \ No newline at end of file diff --git a/src/main/java/fr/inria/corese/core/storage/impl/operations/QueryOperationsImpl.java b/src/main/java/fr/inria/corese/core/storage/impl/operations/QueryOperationsImpl.java new file mode 100644 index 000000000..884a4dc84 --- /dev/null +++ b/src/main/java/fr/inria/corese/core/storage/impl/operations/QueryOperationsImpl.java @@ -0,0 +1,171 @@ +package fr.inria.corese.core.storage.impl.operations; + +import fr.inria.corese.core.Graph; +import fr.inria.corese.core.kgram.api.core.Edge; +import fr.inria.corese.core.kgram.api.core.Node; +import fr.inria.corese.core.storage.api.dataManager.support.exception.ErrorCode; +import fr.inria.corese.core.storage.api.dataManager.support.model.EdgePattern; +import fr.inria.corese.core.storage.api.dataManager.operations.QueryOperations; +import fr.inria.corese.core.storage.api.dataManager.support.exception.DataManagerException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +/** + * Implementation of query operations for CoreseGraphDataManager. + * Converts Graph's Iterable-based API to modern Stream-based API. + */ +public class QueryOperationsImpl implements QueryOperations { + + private static final Logger logger = LoggerFactory.getLogger(QueryOperationsImpl.class); + + private final Graph graph; + + /** + * Constructs query operations for a graph. + * + * @param graph Graph to query + * @throws IllegalArgumentException if graph is null + */ + public QueryOperationsImpl(Graph graph) { + if (graph == null) { + throw new IllegalArgumentException("Graph cannot be null"); + } + this.graph = graph; + } + + @Override + public Stream query(EdgePattern pattern) throws DataManagerException { + if (pattern == null) { + throw new IllegalArgumentException("Pattern cannot be null"); + } + + try { + logger.debug("Querying with pattern: {}", pattern); + + // Extract pattern components + Node subject = pattern.getSubject(); + Node predicate = pattern.getPredicate(); + Node object = pattern.getObject(); + List contexts = pattern.getContexts(); + + // Use Graph's iterate method + Iterable iterable = graph.iterate(subject, predicate, object, contexts); + + // Convert Iterable to Stream + // parallel=false because Graph iteration may not be thread-safe + Stream stream = StreamSupport.stream(iterable.spliterator(), false); + + logger.debug("Query executed successfully"); + return stream; + + } catch (Exception e) { + logger.error("Failed to execute query with pattern: {}", pattern, e); + throw new DataManagerException( + ErrorCode.QUERY_FAILED, + "Failed to execute query: " + e.getMessage(), + e + ); + } + } + + @Override + public long count(EdgePattern pattern) throws DataManagerException { + if (pattern == null) { + throw new IllegalArgumentException("Pattern cannot be null"); + } + + try { + logger.debug("Counting with pattern: {}", pattern); + + // Optimize: if only predicate is specified, use Graph.size(predicate) + if (!pattern.isPredicate() && pattern.isSubject() && + pattern.isObject() && pattern.isContexts()) { + + int count = graph.size(pattern.getPredicate()); + logger.debug("Count (optimized): {}", count); + return count; + } + + // Optimize: if no constraints, use Graph.size() + if (pattern.matchesAll()) { + int count = graph.size(); + logger.debug("Count (total): {}", count); + return count; + } + + // General case: count by iterating + try (Stream stream = query(pattern)) { + long count = stream.count(); + logger.debug("Count (general): {}", count); + return count; + } + + } catch (DataManagerException e) { + throw e; + } catch (Exception e) { + logger.error("Failed to count with pattern: {}", pattern, e); + throw new DataManagerException( + ErrorCode.QUERY_FAILED, + "Failed to count edges: " + e.getMessage(), + e + ); + } + } + + @Override + public boolean exists(EdgePattern pattern) throws DataManagerException { + if (pattern == null) { + throw new IllegalArgumentException("Pattern cannot be null"); + } + + try { + logger.debug("Checking existence with pattern: {}", pattern); + + // Optimize: use findFirst() which stops after first match + try (Stream stream = query(pattern)) { + boolean exists = stream.findFirst().isPresent(); + logger.debug("Exists: {}", exists); + return exists; + } + + } catch (DataManagerException e) { + throw e; + } catch (Exception e) { + logger.error("Failed to check existence with pattern: {}", pattern, e); + throw new DataManagerException( + ErrorCode.QUERY_FAILED, + "Failed to check edge existence: " + e.getMessage(), + e + ); + } + } + + @Override + public Edge find(Edge edge) throws DataManagerException { + if (edge == null) { + throw new IllegalArgumentException("Edge cannot be null"); + } + + try { + logger.debug("Finding edge: {}", edge); + + // Use Graph's find method for RDF-star support + Edge found = graph.find(edge); + + logger.debug("Found: {}", found); + return found != null ? found : edge; + + } catch (Exception e) { + logger.error("Failed to find edge: {}", edge, e); + throw new DataManagerException( + ErrorCode.QUERY_FAILED, + "Failed to find edge: " + e.getMessage(), + e + ); + } + } +} \ No newline at end of file diff --git a/src/main/java/fr/inria/corese/core/storage/impl/transaction/TransactionImpl.java b/src/main/java/fr/inria/corese/core/storage/impl/transaction/TransactionImpl.java new file mode 100644 index 000000000..5fa5b1f6d --- /dev/null +++ b/src/main/java/fr/inria/corese/core/storage/impl/transaction/TransactionImpl.java @@ -0,0 +1,185 @@ +package fr.inria.corese.core.storage.impl.transaction; + +import fr.inria.corese.core.storage.api.dataManager.support.exception.DataManagerException; +import fr.inria.corese.core.storage.api.dataManager.support.exception.ErrorCode; +import fr.inria.corese.core.storage.api.dataManager.transaction.IsolationLevel; +import fr.inria.corese.core.storage.api.dataManager.transaction.Transaction; +import fr.inria.corese.core.storage.api.dataManager.transaction.TransactionState; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.time.Instant; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicReference; + +/** + * Implementation of a transaction handle. + */ +public class TransactionImpl implements Transaction { + + private static final Logger logger = LoggerFactory.getLogger(TransactionImpl.class); + + private final String id; + private final IsolationLevel isolationLevel; + private final Instant startTime; + private final TransactionCallback callback; + + private final AtomicReference state; + + /** + * Callback interface for transaction operations. + * Allows the TransactionManager to be notified of commits/rollbacks. + */ + public interface TransactionCallback { + void onCommit(TransactionImpl transaction) throws DataManagerException; + + void onRollback(TransactionImpl transaction) throws DataManagerException; + } + + /** + * Constructs a new transaction. + * + * @param isolationLevel Isolation level + * @param callback Callback for commit/rollback operations + */ + public TransactionImpl(IsolationLevel isolationLevel, TransactionCallback callback) { + this.id = UUID.randomUUID().toString(); + this.isolationLevel = isolationLevel; + this.startTime = Instant.now(); + this.callback = callback; + this.state = new AtomicReference<>(TransactionState.ACTIVE); + + logger.debug("Transaction {} started with isolation level {}", id, isolationLevel); + } + + @Override + public String getId() { + return id; + } + + @Override + public IsolationLevel getIsolationLevel() { + return isolationLevel; + } + + + @Override + public void commit() throws DataManagerException { + TransactionState currentState = state.get(); + + if (!currentState.isActive()) { + throw new IllegalStateException( + "Cannot commit transaction in state: " + currentState + ); + } + + logger.debug("Committing transaction {}", id); + + try { + // Notify callback + callback.onCommit(this); + + // Update state + if (!state.compareAndSet(TransactionState.ACTIVE, TransactionState.COMMITTED)) { + throw new IllegalStateException( + "Transaction state changed during commit" + ); + } + + logger.info("Transaction {} committed successfully", id); + + } catch (DataManagerException e) { + // Mark as failed + state.set(TransactionState.FAILED); + logger.error("Failed to commit transaction {}", id, e); + throw e; + + } catch (Exception e) { + state.set(TransactionState.FAILED); + logger.error("Unexpected error during commit of transaction {}", id, e); + throw new DataManagerException( + ErrorCode.TRANSACTION_ERROR, + "Failed to commit transaction: " + e.getMessage(), + e + ); + } + } + + @Override + public void rollback() throws DataManagerException { + TransactionState currentState = state.get(); + + if (!currentState.isActive()) { + throw new IllegalStateException( + "Cannot rollback transaction in state: " + currentState + ); + } + + logger.debug("Rolling back transaction {}", id); + + try { + // Notify callback + callback.onRollback(this); + + // Update state + if (!state.compareAndSet(TransactionState.ACTIVE, TransactionState.ROLLED_BACK)) { + throw new IllegalStateException( + "Transaction state changed during rollback" + ); + } + + logger.info("Transaction {} rolled back successfully", id); + + } catch (DataManagerException e) { + state.set(TransactionState.FAILED); + logger.error("Failed to rollback transaction {}", id, e); + throw e; + + } catch (Exception e) { + state.set(TransactionState.FAILED); + logger.error("Unexpected error during rollback of transaction {}", id, e); + throw new DataManagerException( + ErrorCode.TRANSACTION_ERROR, + "Failed to rollback transaction: " + e.getMessage(), + e + ); + } + } + + @Override + public boolean isActive() { + return state.get().isActive(); + } + + @Override + public TransactionState getState() { + return state.get(); + } + + @Override + public void close() throws DataManagerException { + TransactionState currentState = state.get(); + + if (currentState.isActive()) { + logger.warn("Transaction {} not committed, performing automatic rollback", id); + try { + rollback(); + } catch (DataManagerException e) { + logger.error("Failed to auto-rollback transaction {}", id, e); + throw e; + } + } + + logger.debug("Transaction {} closed in state {}", id, state.get()); + } + + @Override + public String toString() { + return "Transaction{" + + "id='" + id + '\'' + + ", isolationLevel=" + isolationLevel + + ", state=" + state.get() + + ", startTime=" + startTime + + '}'; + } +} \ No newline at end of file diff --git a/src/main/java/fr/inria/corese/core/storage/impl/transaction/TransactionManagerImpl.java b/src/main/java/fr/inria/corese/core/storage/impl/transaction/TransactionManagerImpl.java new file mode 100644 index 000000000..ada4b50f0 --- /dev/null +++ b/src/main/java/fr/inria/corese/core/storage/impl/transaction/TransactionManagerImpl.java @@ -0,0 +1,191 @@ +package fr.inria.corese.core.storage.impl.transaction; + +import fr.inria.corese.core.Graph; +import fr.inria.corese.core.storage.api.dataManager.support.exception.DataManagerException; +import fr.inria.corese.core.storage.api.dataManager.support.exception.ErrorCode; +import fr.inria.corese.core.storage.api.dataManager.transaction.IsolationLevel; +import fr.inria.corese.core.storage.api.dataManager.transaction.Transaction; +import fr.inria.corese.core.storage.api.dataManager.transaction.TransactionManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.EnumSet; +import java.util.Optional; +import java.util.Set; + +/** + * Transaction manager implementation for CoreseGraphDataManager. + */ +public class TransactionManagerImpl implements TransactionManager { + + private static final Logger logger = LoggerFactory.getLogger(TransactionManagerImpl.class); + + private final Graph graph; + private final boolean transactionSupport; + private final IsolationLevel defaultIsolationLevel; + + // Thread-local storage for current transaction per thread + private final ThreadLocal currentTransaction = new ThreadLocal<>(); + + /** + * Constructs a transaction manager. + * + * @param graph Graph to manage + * @param transactionSupport Whether transactions are enabled + * @param defaultIsolationLevel Default isolation level + */ + public TransactionManagerImpl( + Graph graph, + boolean transactionSupport, + IsolationLevel defaultIsolationLevel) { + + if (graph == null) { + throw new IllegalArgumentException("Graph cannot be null"); + } + if (defaultIsolationLevel == null) { + throw new IllegalArgumentException("Default isolation level cannot be null"); + } + + this.graph = graph; + this.transactionSupport = transactionSupport; + this.defaultIsolationLevel = defaultIsolationLevel; + + logger.info("TransactionManager initialized: support={}, defaultLevel={}", + transactionSupport, defaultIsolationLevel); + } + + @Override + public boolean supportsTransactions() { + return transactionSupport; + } + + @Override + public Transaction beginTransaction() throws DataManagerException { + return beginTransaction(defaultIsolationLevel); + } + + @Override + public Transaction beginTransaction(IsolationLevel isolationLevel) throws DataManagerException { + if (!supportsTransactions()) { + throw new UnsupportedOperationException("Transactions are not supported"); + } + + if (isolationLevel == null) { + throw new IllegalArgumentException("Isolation level cannot be null"); + } + + if (!getSupportedIsolationLevels().contains(isolationLevel)) { + throw new IllegalArgumentException( + "Isolation level " + isolationLevel + " is not supported" + ); + } + + // Check if there's already an active transaction + if (hasActiveTransaction()) { + TransactionImpl existing = currentTransaction.get(); + logger.warn("Starting new transaction while transaction {} is still active", + existing.getId()); + // For now, we don't support nested transactions + throw new DataManagerException( + ErrorCode.TRANSACTION_ERROR, + "Nested transactions are not supported. " + + "Current transaction: " + existing.getId() + ); + } + + TransactionImpl.TransactionCallback callback = new TransactionImpl.TransactionCallback() { + @Override + public void onCommit(TransactionImpl transaction) throws DataManagerException { + performCommit(transaction); + } + + @Override + public void onRollback(TransactionImpl transaction) throws DataManagerException { + performRollback(transaction); + } + }; + + // Create and register transaction + TransactionImpl transaction = new TransactionImpl(isolationLevel, callback); + currentTransaction.set(transaction); + + logger.info("Started transaction {} with isolation level {}", + transaction.getId(), isolationLevel); + + return transaction; + } + + @Override + public Optional getCurrentTransaction() { + return Optional.ofNullable(currentTransaction.get()); + } + + @Override + public IsolationLevel getDefaultIsolationLevel() { + return defaultIsolationLevel; + } + + @Override + public Set getSupportedIsolationLevels() { + // For now, we support all isolation levels + // In a real implementation, this would depend on the backend storage + return EnumSet.allOf(IsolationLevel.class); + } + + /** + * Performs the actual commit operation. + * Called by the transaction when commit() is invoked. + * + * @param transaction Transaction to commit + * @throws DataManagerException if commit fails + */ + private void performCommit(TransactionImpl transaction) throws DataManagerException { + try { + logger.debug("Performing commit for transaction {}", transaction.getId()); + + graph.init(); + + // Clear thread-local + currentTransaction.remove(); + + logger.debug("Commit completed for transaction {}", transaction.getId()); + + } catch (Exception e) { + logger.error("Failed to commit transaction {}", transaction.getId(), e); + throw new DataManagerException( + ErrorCode.TRANSACTION_ERROR, + "Failed to commit transaction: " + e.getMessage(), + e + ); + } + } + + /** + * Performs the actual rollback operation. + * Called by the transaction when rollback() is invoked. + * + * @param transaction Transaction to rollback + * @throws DataManagerException if rollback fails + */ + private void performRollback(TransactionImpl transaction) throws DataManagerException { + try { + logger.debug("Performing rollback for transaction {}", transaction.getId()); + + graph.init(); + + // Clear thread-local + currentTransaction.remove(); + + logger.debug("Rollback completed for transaction {}", transaction.getId()); + + } catch (Exception e) { + logger.error("Failed to rollback transaction {}", transaction.getId(), e); + throw new DataManagerException( + ErrorCode.TRANSACTION_ERROR, + "Failed to rollback transaction: " + e.getMessage(), + e + ); + } + } + +} \ No newline at end of file From 4d48e52b4ab4b86b2219a06b618c0343967de874 Mon Sep 17 00:00:00 2001 From: "AD\\aabdoun" Date: Mon, 12 Jan 2026 11:41:33 +0100 Subject: [PATCH 3/8] Refactor DataManager architecture ajouter java doc et test unitaire --- .../impl/lifecycle/LifecycleManagerImpl.java | 91 ++++--- .../impl/operations/BulkOperationsImpl.java | 85 ++++++- .../operations/MetadataOperationsImpl.java | 64 +++-- .../operations/MutationOperationsImpl.java | 72 ++++-- .../impl/operations/QueryOperationsImpl.java | 61 +++-- .../impl/transaction/TransactionImpl.java | 58 +++-- .../transaction/TransactionManagerImpl.java | 59 ++--- .../corese/core/query/QueryEngineTest.java | 1 + .../lifecycle/LifecycleManagerImplTest.java | 175 ++++++++++++++ .../operations/BulkOperationsImplTest.java | 226 ++++++++++++++++++ 10 files changed, 767 insertions(+), 125 deletions(-) create mode 100644 src/test/java/fr/inria/corese/core/storage/impl/lifecycle/LifecycleManagerImplTest.java create mode 100644 src/test/java/fr/inria/corese/core/storage/impl/operations/BulkOperationsImplTest.java diff --git a/src/main/java/fr/inria/corese/core/storage/impl/lifecycle/LifecycleManagerImpl.java b/src/main/java/fr/inria/corese/core/storage/impl/lifecycle/LifecycleManagerImpl.java index 3c5bdffca..1cf2c88de 100644 --- a/src/main/java/fr/inria/corese/core/storage/impl/lifecycle/LifecycleManagerImpl.java +++ b/src/main/java/fr/inria/corese/core/storage/impl/lifecycle/LifecycleManagerImpl.java @@ -10,30 +10,35 @@ import org.slf4j.LoggerFactory; /** - * Lifecycle manager implementation for CoreseGraphDataManager. - * Manages initialization, state and shutdown of the DataManager. + * Implementation for Corese Graph Data Management. + * This class is responsible for controlling the transitions between different stages + * of the DataManager's life, including initialization, operational state, and shutdown. + * It ensures thread-safe state transitions using internal synchronization. */ public class LifecycleManagerImpl implements DataManagerLifecycle { private static final Logger logger = LoggerFactory.getLogger(LifecycleManagerImpl.class); - // Reference to the graph to manage + /** Reference to the Corese Graph instance being managed. */ private final Graph graph; - // Current lifecycle state (volatile for thread-safety) + /** * The current state of the lifecycle. + * Marked as {@code volatile} to ensure visibility across different threads. + */ private volatile LifecycleState state; - // Current configuration + /** The configuration applied to the DataManager during initialization. */ private DataManagerConfig config; - // Lock for state change synchronization + /** Lock object used to synchronize state-changing operations. */ private final Object stateLock = new Object(); /** - * Constructs a lifecycle manager for a graph. + * Constructs a new LifecycleManager for the specified graph. + * Initial state is set to {@link LifecycleState#NOT_INITIALIZED}. * - * @param graph Corese Graph to manage - * @throws IllegalArgumentException if graph is null + * @param graph the Corese Graph to be managed; must not be null. + * @throws IllegalArgumentException if the provided graph is null. */ public LifecycleManagerImpl(Graph graph) { if (graph == null) { @@ -43,6 +48,16 @@ public LifecycleManagerImpl(Graph graph) { this.state = LifecycleState.NOT_INITIALIZED; } + /** + * Initializes the DataManager with the provided configuration. + * This method transitions the state from {@code NOT_INITIALIZED} to {@code INITIALIZING}, + * performs the setup logic, and finally moves to the {@code RUNNING} state. + * + * @param config the configuration settings to apply; must not be null. + * @throws DataManagerException if an error occurs during the graph initialization process. + * @throws IllegalStateException if the manager is already initialized or currently initializing. + * @throws IllegalArgumentException if the provided config is null. + */ @Override public void initialize(DataManagerConfig config) throws DataManagerException { if (config == null) { @@ -52,10 +67,10 @@ public void initialize(DataManagerConfig config) throws DataManagerException { synchronized (stateLock) { // State verification if (state == LifecycleState.RUNNING) { - throw new IllegalStateException("Already initialized"); + throw new IllegalStateException("DataManager is already initialized"); } if (state == LifecycleState.INITIALIZING) { - throw new IllegalStateException("Already initializing"); + throw new IllegalStateException("DataManager is already in the process of initializing"); } logger.info("Initializing DataManager with config: {}", config); @@ -65,18 +80,18 @@ public void initialize(DataManagerConfig config) throws DataManagerException { // Save configuration this.config = config; - // Initialize the graph + // Initialize the underlying Corese graph graph.init(); - // Apply specific configurations + // Apply specific configuration settings applyConfiguration(config); - // Move to RUNNING state + // Transition to RUNNING state state = LifecycleState.RUNNING; logger.info("DataManager initialized successfully"); } catch (Exception e) { - // Restore state on error + // Rollback state on failure state = LifecycleState.NOT_INITIALIZED; this.config = null; @@ -90,31 +105,43 @@ public void initialize(DataManagerConfig config) throws DataManagerException { } } + /** + * Checks if the DataManager has been successfully initialized and is currently running. + * + * @return {@code true} if the current state is {@link LifecycleState#RUNNING}, {@code false} otherwise. + */ @Override public boolean isInitialized() { return state == LifecycleState.RUNNING; } + /** + * Shuts down the DataManager and releases associated resources. + * Transition logic: {@code RUNNING} -> {@code SHUTTING_DOWN} -> {@code SHUTDOWN}. + * + * @throws DataManagerException if an error occurs during the shutdown process. + * @throws IllegalStateException if the DataManager is not in a RUNNING state. + */ @Override public void shutdown() throws DataManagerException { synchronized (stateLock) { if (state != LifecycleState.RUNNING) { - throw new IllegalStateException("Not running, current state: " + state); + throw new IllegalStateException("DataManager cannot be shut down because it is in state: " + state); } logger.info("Shutting down DataManager"); state = LifecycleState.SHUTTING_DOWN; try { - // Clean up resources + // Perform cleanup (re-init often acts as a reset/cleanup in Corese Graph) graph.init(); - // Move to SHUTDOWN state + // Transition to final SHUTDOWN state state = LifecycleState.SHUTDOWN; logger.info("DataManager shut down successfully"); } catch (Exception e) { - // On error, stay in SHUTTING_DOWN + // Remain in SHUTTING_DOWN state to indicate a partial or failed shutdown logger.error("Failed to shutdown DataManager cleanly", e); throw new DataManagerException( ErrorCode.SHUTDOWN_FAILED, @@ -125,28 +152,38 @@ public void shutdown() throws DataManagerException { } } + /** + * Returns the current lifecycle state of the DataManager. + * + * @return the current + */ @Override public LifecycleState getState() { return state; } + /** + * Retrieves the configuration used to initialize the DataManager. + * + * @return the {@link DataManagerConfig} instance, or {@code null} if not yet initialized. + */ @Override public DataManagerConfig getConfig() { return config; } /** - * Applies the configuration to the graph. - * This method can be extended to support more options. + * Internal helper method to apply configuration parameters to the managed graph. + * Logs debug information and storage paths. * - * @param config Configuration to apply + * @param config the configuration to apply. */ private void applyConfiguration(DataManagerConfig config) { if (config.isDebug()) { logger.debug("Debug mode enabled"); } - // Storage path (for information) + // Log storage path information logger.info("Storage path: {}", config.getStoragePath()); // Other configurations can be added here @@ -154,15 +191,15 @@ private void applyConfiguration(DataManagerConfig config) { } /** - * Checks that the DataManager is in a usable state. - * Throws an exception if not. + * Validates that the DataManager is in a state where it can accept data operations. + * Use this method before any read/write access to the underlying storage. * - * @throws IllegalStateException if not in RUNNING state + * @throws IllegalStateException if the current state is not usable (e.g., NOT_INITIALIZED or SHUTDOWN). */ public void checkUsable() { if (!state.isUsable()) { throw new IllegalStateException( - "DataManager is not usable, current state: " + state + "DataManager is not in a usable state. Current state: " + state ); } } diff --git a/src/main/java/fr/inria/corese/core/storage/impl/operations/BulkOperationsImpl.java b/src/main/java/fr/inria/corese/core/storage/impl/operations/BulkOperationsImpl.java index dc64265c6..e69de0ca4 100644 --- a/src/main/java/fr/inria/corese/core/storage/impl/operations/BulkOperationsImpl.java +++ b/src/main/java/fr/inria/corese/core/storage/impl/operations/BulkOperationsImpl.java @@ -21,13 +21,14 @@ public class BulkOperationsImpl implements BulkOperations { private static final Logger logger = LoggerFactory.getLogger(BulkOperationsImpl.class); + /** The underlying Corese graph where operations are performed. */ private final Graph graph; /** - * Constructs bulk operations for a graph. + * Constructs a new bulk operations handler for the specified graph. * - * @param graph Graph to operate on - * @throws IllegalArgumentException if graph is null + * @param graph the Corese Graph instance to operate on; must not be null. + * @throws IllegalArgumentException if the provided graph is null. */ public BulkOperationsImpl(Graph graph) { if (graph == null) { @@ -36,6 +37,14 @@ public BulkOperationsImpl(Graph graph) { this.graph = graph; } + /** + * Inserts a list of edges into the graph in a batch. + * + * @param edges the list of edges to insert; must not be null or empty. + * @return a {@link MutationResult} summarizing the successes and failures of the batch operation. + * @throws DataManagerException if a critical system error occurs during insertion. + * @throws IllegalArgumentException if the edges list is null or empty. + */ @Override public MutationResult insertBatch(List edges) throws DataManagerException { if (edges == null || edges.isEmpty()) { @@ -77,6 +86,14 @@ public MutationResult insertBatch(List edges) throws DataManagerException } } + /** + * Deletes a list of specific edges from the graph. + * + * @param edges the list of edges to remove; must not be null or empty. + * @return a {@link MutationResult} summarizing the edges removed and any failures. + * @throws DataManagerException if a critical system error occurs during deletion. + * @throws IllegalArgumentException if the edges list is null or empty. + */ @Override public MutationResult deleteBatch(List edges) throws DataManagerException { if (edges == null || edges.isEmpty()) { @@ -127,6 +144,13 @@ public MutationResult deleteBatch(List edges) throws DataManagerException } } + /** + * Deletes all edges matching the specified pattern. + * + * @param pattern the pattern defining subject, predicate, object, and/or context filters. + * @return a {@link MutationResult} containing the list of affected edges. + * @throws DataManagerException if the pattern-based deletion fails. + */ @Override public MutationResult deleteByPattern(EdgePattern pattern) throws DataManagerException { if (pattern == null) { @@ -178,6 +202,14 @@ public MutationResult deleteByPattern(EdgePattern pattern) throws DataManagerExc } } + /** + * Clears specific named graphs (contexts) from the system. + * + * @param contexts the list of context nodes to clear. If null or empty, clears all data. + * @param silent if true, suppresses errors when a specific context does not exist. + * @return a {@link MutationResult} summarizing the operation. + * @throws DataManagerException if a context clear operation fails critically. + */ @Override public MutationResult clearContexts(List contexts, boolean silent) throws DataManagerException { @@ -223,6 +255,12 @@ public MutationResult clearContexts(List contexts, boolean silent) } } + /** + * Deletes all data from the graph, including all edges and all named graphs. + * + * @return a {@link MutationResult} reflecting the state before and after clear. + * @throws DataManagerException if the clearing operation fails. + */ @Override public MutationResult clearAll() throws DataManagerException { logger.warn("Clearing ALL data from graph"); @@ -252,6 +290,15 @@ public MutationResult clearAll() throws DataManagerException { } } + /** + * Merges the contents of a source graph into a target graph. + * + * @param sourceContext the URI/Label of the source graph. + * @param targetContext the URI/Label of the target graph. + * @param silent if true, ignores missing source graphs. + * @return a {@link MutationResult} representing the merge outcome. + * @throws DataManagerException if the operation fails at the graph level. + */ @Override public MutationResult addGraph(Node sourceContext, Node targetContext, boolean silent) throws DataManagerException { @@ -293,6 +340,15 @@ public MutationResult addGraph(Node sourceContext, Node targetContext, boolean s } } + /** + * Replaces the contents of a target graph with a copy of the source graph. + * + * @param sourceContext the URI/Label of the source graph. + * @param targetContext the URI/Label of the target graph. + * @param silent if true, ignores missing source graphs. + * @return a {@link MutationResult} representing the copy outcome. + * @throws DataManagerException if the operation fails at the graph level. + */ @Override public MutationResult copyGraph(Node sourceContext, Node targetContext, boolean silent) throws DataManagerException { @@ -334,6 +390,15 @@ public MutationResult copyGraph(Node sourceContext, Node targetContext, boolean } } + /** + * Moves the contents of a source graph to a target graph, clearing the source graph. + * + * @param sourceContext the URI/Label of the source graph. + * @param targetContext the URI/Label of the target graph. + * @param silent if true, ignores missing source graphs. + * @return a {@link MutationResult} representing the move outcome. + * @throws DataManagerException if the operation fails at the graph level. + */ @Override public MutationResult moveGraph(Node sourceContext, Node targetContext, boolean silent) throws DataManagerException { @@ -375,6 +440,13 @@ public MutationResult moveGraph(Node sourceContext, Node targetContext, boolean } } + /** + * Explicitly declares a new context (named graph) in the system without adding edges. + * + * @param context the context node to declare. + * @return a successful {@link MutationResult}. + * @throws DataManagerException if the declaration fails. + */ @Override public MutationResult declareContext(Node context) throws DataManagerException { if (context == null) { @@ -398,6 +470,13 @@ public MutationResult declareContext(Node context) throws DataManagerException { } } + /** + * Removes a context declaration and all its associated edges. + * + * @param context the context node to undeclare. + * @return a {@link MutationResult} summarizing the removed edges. + * @throws DataManagerException if the undeclare operation fails. + */ @Override public MutationResult undeclareContext(Node context) throws DataManagerException { if (context == null) { diff --git a/src/main/java/fr/inria/corese/core/storage/impl/operations/MetadataOperationsImpl.java b/src/main/java/fr/inria/corese/core/storage/impl/operations/MetadataOperationsImpl.java index 35bd638f2..ead905f80 100644 --- a/src/main/java/fr/inria/corese/core/storage/impl/operations/MetadataOperationsImpl.java +++ b/src/main/java/fr/inria/corese/core/storage/impl/operations/MetadataOperationsImpl.java @@ -22,13 +22,16 @@ public class MetadataOperationsImpl implements MetadataOperations { private static final Logger logger = LoggerFactory.getLogger(MetadataOperationsImpl.class); + /** + * The underlying Corese graph instance. + */ private final Graph graph; /** - * Constructs metadata operations for a graph. + * Constructs a new metadata operations handler for the specified graph. * - * @param graph Graph to query - * @throws IllegalArgumentException if graph is null + * @param graph the Corese Graph to query; must not be null. + * @throws IllegalArgumentException if the provided graph is null. */ public MetadataOperationsImpl(Graph graph) { if (graph == null) { @@ -37,19 +40,25 @@ public MetadataOperationsImpl(Graph graph) { this.graph = graph; } + /** + * Retrieves all unique predicates used within the specified context. + * + * @param context the context (named graph) node. Note: Standard Corese implementation currently returns all predicates. + * @return an unmodifiable Set of predicate nodes. + * @throws DataManagerException if the metadata retrieval fails. + */ @Override public Set getPredicates(Node context) throws DataManagerException { try { logger.debug("Getting predicates for context: {}", context); - // Graph.getSortedProperties() returns all predicates - // Note: context parameter is not used by Graph implementation + // Graph.getSortedProperties() returns all predicates in the graph Iterable iterable = graph.getSortedProperties(); - // Convert to Set + // Convert to Set to ensure uniqueness and provide standard API access Set predicates = iterableToSet(iterable); - logger.debug("Found {} predicates", predicates.size()); + logger.debug("Found {} unique predicates", predicates.size()); return Collections.unmodifiableSet(predicates); } catch (Exception e) { @@ -62,6 +71,13 @@ public Set getPredicates(Node context) throws DataManagerException { } } + /** + * Retrieves all unique nodes (subjects or objects) within the specified context. + * + * @param context the context node to filter by; if null, retrieves nodes from all contexts. + * @return an unmodifiable Set of nodes. + * @throws DataManagerException if the node retrieval fails. + */ @Override public Set getNodes(Node context) throws DataManagerException { try { @@ -69,15 +85,14 @@ public Set getNodes(Node context) throws DataManagerException { Iterable iterable; if (context == null) { - // All nodes + // Retrieves an iterator over all nodes in the entire graph iterable = graph.getNodeGraphIterator(); } else { - // Nodes in specific context + // Retrieves an iterator over nodes specific to the named graph Node graphNode = graph.getNode(context); iterable = graph.getNodeGraphIterator(graphNode); } - // Convert to Set Set nodes = iterableToSet(iterable); logger.debug("Found {} nodes", nodes.size()); @@ -93,15 +108,20 @@ public Set getNodes(Node context) throws DataManagerException { } } + /** + * Retrieves all named graph identifiers (contexts) currently defined in the system. + * + * @return an unmodifiable Set of context nodes. + * @throws DataManagerException if the context retrieval fails. + */ @Override public Set getContexts() throws DataManagerException { try { logger.debug("Getting all contexts"); - // Graph.getGraphNodes() returns all named graphs + // Graph.getGraphNodes() returns nodes representing named graphs Iterable iterable = graph.getGraphNodes(new ArrayList<>(0)); - // Convert to Set Set contexts = iterableToSet(iterable); logger.debug("Found {} contexts", contexts.size()); @@ -117,18 +137,24 @@ public Set getContexts() throws DataManagerException { } } + /** + * Collects and returns general statistics about the graph size and density. + * + * @return a {@link GraphStatistics} object containing counts for edges, nodes, predicates, and contexts. + * @throws DataManagerException if statistics collection fails. + */ @Override public GraphStatistics getStatistics() throws DataManagerException { try { logger.debug("Collecting graph statistics"); - // Collect statistics + // Aggregate metrics from current graph state long edgeCount = graph.size(); long nodeCount = getNodes(null).size(); long predicateCount = getPredicates(null).size(); long contextCount = getContexts().size(); - // Build GraphStatistics with Builder pattern + // Build GraphStatistics using the builder pattern GraphStatistics stats = GraphStatistics.builder() .edgeCount(edgeCount) .nodeCount(nodeCount) @@ -136,7 +162,7 @@ public GraphStatistics getStatistics() throws DataManagerException { .contextCount(contextCount) .build(); - logger.debug("Statistics: {}", stats); + logger.debug("Statistics collected: {}", stats); return stats; } catch (Exception e) { @@ -150,11 +176,11 @@ public GraphStatistics getStatistics() throws DataManagerException { } /** - * Converts an Iterable to a Set. - * Helper method to convert Graph's Iterable results to Sets. + * Helper method to convert a Corese {@link Iterable} of nodes into a {@link HashSet}. + * Ensures that null nodes are ignored and duplicates are removed. * - * @param iterable Iterable to convert - * @return Set containing all elements + * @param iterable the iterable to convert. + * @return a Set containing the extracted nodes. */ private Set iterableToSet(Iterable iterable) { Set set = new HashSet<>(); diff --git a/src/main/java/fr/inria/corese/core/storage/impl/operations/MutationOperationsImpl.java b/src/main/java/fr/inria/corese/core/storage/impl/operations/MutationOperationsImpl.java index efba36911..61be81e1a 100644 --- a/src/main/java/fr/inria/corese/core/storage/impl/operations/MutationOperationsImpl.java +++ b/src/main/java/fr/inria/corese/core/storage/impl/operations/MutationOperationsImpl.java @@ -20,13 +20,14 @@ public class MutationOperationsImpl implements MutationOperations { private static final Logger logger = LoggerFactory.getLogger(MutationOperationsImpl.class); + /** The underlying Corese graph instance to be mutated. */ private final Graph graph; /** - * Constructs mutation operations for a graph. + * Constructs mutation operations for a specific graph. * - * @param graph Graph to mutate - * @throws IllegalArgumentException if graph is null + * @param graph the Corese Graph to mutate; must not be null. + * @throws IllegalArgumentException if the provided graph is null. */ public MutationOperationsImpl(Graph graph) { if (graph == null) { @@ -35,6 +36,13 @@ public MutationOperationsImpl(Graph graph) { this.graph = graph; } + /** + * Inserts a single edge into the graph. + * + * @param edge the edge object to insert. + * @return a {@link MutationResult} indicating success or failure. + * @throws DataManagerException if the insertion fails at the storage level. + */ @Override public MutationResult insertEdge(Edge edge) throws DataManagerException { if (edge == null) { @@ -44,7 +52,7 @@ public MutationResult insertEdge(Edge edge) throws DataManagerException { try { logger.debug("Inserting edge: {}", edge); - // Use Graph's insertEdgeWithTargetNode + // Use Graph's specific method for inserting an edge while ensuring target nodes exist Edge inserted = graph.insertEdgeWithTargetNode(edge); if (inserted != null) { @@ -65,6 +73,16 @@ public MutationResult insertEdge(Edge edge) throws DataManagerException { } } + /** + * Inserts an edge defined by its individual components into the specified contexts. + * + * @param subject the subject node. + * @param predicate the predicate node. + * @param object the object node. + * @param contexts the list of context (named graph) nodes where the edge should be stored. + * @return a {@link MutationResult} summarizing the insertion. + * @throws DataManagerException if the insertion fails. + */ @Override public MutationResult insertEdge(Node subject, Node predicate, Node object, List contexts) throws DataManagerException { @@ -77,13 +95,13 @@ public MutationResult insertEdge(Node subject, Node predicate, Node object, List logger.debug("Inserting edge: ({}, {}, {}) in contexts: {}", subject, predicate, object, contexts); - // Track size before insert + // Capture graph size to verify insertion if the result set is ambiguous int sizeBefore = graph.size(); - // Use Graph's insert method + // Perform the insertion in the Corese graph Iterable inserted = graph.insert(subject, predicate, object, contexts); - // Convert Iterable to List + // Process returned edges List insertedList = new ArrayList<>(); if (inserted != null) { for (Edge e : inserted) { @@ -93,20 +111,18 @@ public MutationResult insertEdge(Node subject, Node predicate, Node object, List } } - // Check if insertion actually happened int sizeAfter = graph.size(); boolean insertionOccurred = (sizeAfter > sizeBefore) || !insertedList.isEmpty(); if (insertionOccurred) { logger.debug("Inserted edge successfully"); - // If we got edges back, use them if (!insertedList.isEmpty()) { if (insertedList.size() == 1) { return MutationResult.success(insertedList.getFirst(), "Edge inserted"); } - // Multiple edges (multiple contexts) + // Handle multi-context insertion results MutationResult.BulkBuilder builder = MutationResult.bulkBuilder() .totalAttempted(insertedList.size()) .addAffectedEdges(insertedList) @@ -118,7 +134,7 @@ public MutationResult insertEdge(Node subject, Node predicate, Node object, List return builder.build(); } else { - // Insertion happened but no edges returned - create edge reference + // Fallback edge creation for result reporting if the iterator was empty but size changed Edge edge = graph.create( contexts != null && !contexts.isEmpty() ? contexts.getFirst() : graph.getDefaultGraphNode(), subject, predicate, object @@ -127,7 +143,6 @@ public MutationResult insertEdge(Node subject, Node predicate, Node object, List } } else { logger.warn("Edge insertion did not change graph size (possible duplicate)"); - // May be a duplicate - still return success with note Edge edge = graph.create( contexts != null && !contexts.isEmpty() ? contexts.getFirst() : graph.getDefaultGraphNode(), subject, predicate, object @@ -145,6 +160,13 @@ public MutationResult insertEdge(Node subject, Node predicate, Node object, List } } + /** + * Deletes a specific edge object from the graph. + * + * @param edge the edge to delete. + * @return a {@link MutationResult} indicating if the edge was found and deleted. + * @throws DataManagerException if the deletion fails. + */ @Override public MutationResult deleteEdge(Edge edge) throws DataManagerException { if (edge == null) { @@ -154,10 +176,9 @@ public MutationResult deleteEdge(Edge edge) throws DataManagerException { try { logger.debug("Deleting edge: {}", edge); - // Use Graph's deleteEdgeWithTargetNode + // Corese graph returns an Iterable of deleted edges Iterable deleted = graph.deleteEdgeWithTargetNode(edge); - // Convert to list List deletedList = new ArrayList<>(); if (deleted != null) { for (Edge e : deleted) { @@ -168,7 +189,6 @@ public MutationResult deleteEdge(Edge edge) throws DataManagerException { } if (!deletedList.isEmpty()) { - // Return first deleted edge (typically only one) return MutationResult.success(deletedList.getFirst(), "Edge deleted"); } else { logger.warn("Edge deletion returned empty (edge may not exist)"); @@ -185,6 +205,17 @@ public MutationResult deleteEdge(Edge edge) throws DataManagerException { } } + /** + * Deletes edges matching the specified subject, predicate, and object in the given contexts. + * Supports wildcards (null values). + * + * @param subject the subject node (or null for wildcard). + * @param predicate the predicate node (or null for wildcard). + * @param object the object node (or null for wildcard). + * @param contexts the list of contexts to search in. + * @return a {@link MutationResult} summarizing all deleted edges. + * @throws DataManagerException if the deletion process fails. + */ @Override public MutationResult deleteEdges(Node subject, Node predicate, Node object, List contexts) throws DataManagerException { @@ -193,13 +224,13 @@ public MutationResult deleteEdges(Node subject, Node predicate, Node object, Lis logger.debug("Deleting edges: ({}, {}, {}) in contexts: {}", subject, predicate, object, contexts); - // Use graph.iterate() to find matching edges (supports null wildcards) + // Iterate over matching edges based on pattern Iterable matchingEdges = graph.iterate(subject, predicate, object, contexts); - // Delete each matching edge List deletedList = new ArrayList<>(); for (Edge edge : matchingEdges) { if (edge != null) { + // Delete the specific edge instance List deleted = graph.delete(edge); if (deleted != null) { for (Edge e : deleted) { @@ -213,7 +244,6 @@ public MutationResult deleteEdges(Node subject, Node predicate, Node object, Lis logger.debug("Deleted {} edge(s)", deletedList.size()); - // Build bulk result MutationResult.BulkBuilder builder = MutationResult.bulkBuilder() .totalAttempted(deletedList.size()) .addAffectedEdges(deletedList) @@ -235,6 +265,12 @@ public MutationResult deleteEdges(Node subject, Node predicate, Node object, Lis } } + /** + * Generates a unique blank node identifier within the graph context. + * + * @return a new blank node ID string. + * @throws DataManagerException if the ID generation fails. + */ @Override public String generateBlankNode() throws DataManagerException { try { diff --git a/src/main/java/fr/inria/corese/core/storage/impl/operations/QueryOperationsImpl.java b/src/main/java/fr/inria/corese/core/storage/impl/operations/QueryOperationsImpl.java index 884a4dc84..fffd69a48 100644 --- a/src/main/java/fr/inria/corese/core/storage/impl/operations/QueryOperationsImpl.java +++ b/src/main/java/fr/inria/corese/core/storage/impl/operations/QueryOperationsImpl.java @@ -22,13 +22,14 @@ public class QueryOperationsImpl implements QueryOperations { private static final Logger logger = LoggerFactory.getLogger(QueryOperationsImpl.class); + /** The underlying Corese graph instance to be queried. */ private final Graph graph; /** - * Constructs query operations for a graph. + * Constructs query operations for a specific graph. * - * @param graph Graph to query - * @throws IllegalArgumentException if graph is null + * @param graph the Corese Graph instance to query; must not be null. + * @throws IllegalArgumentException if the provided graph is null. */ public QueryOperationsImpl(Graph graph) { if (graph == null) { @@ -37,6 +38,14 @@ public QueryOperationsImpl(Graph graph) { this.graph = graph; } + /** + * Executes a query based on the provided edge pattern and returns a stream of results. + * + * @param pattern the pattern defining filters for subject, predicate, object, and contexts. + * @return a {@link Stream} of {@link Edge} objects matching the pattern. + * @throws DataManagerException if the query fails at the graph level. + * @throws IllegalArgumentException if the pattern is null. + */ @Override public Stream query(EdgePattern pattern) throws DataManagerException { if (pattern == null) { @@ -46,17 +55,16 @@ public Stream query(EdgePattern pattern) throws DataManagerException { try { logger.debug("Querying with pattern: {}", pattern); - // Extract pattern components + // Extract pattern components for the graph iterator Node subject = pattern.getSubject(); Node predicate = pattern.getPredicate(); Node object = pattern.getObject(); List contexts = pattern.getContexts(); - // Use Graph's iterate method + // Obtain the iterator from the Corese Graph Iterable iterable = graph.iterate(subject, predicate, object, contexts); - // Convert Iterable to Stream - // parallel=false because Graph iteration may not be thread-safe + // Convert the Iterable to a Stream for modern API usage Stream stream = StreamSupport.stream(iterable.spliterator(), false); logger.debug("Query executed successfully"); @@ -72,6 +80,15 @@ public Stream query(EdgePattern pattern) throws DataManagerException { } } + /** + * Counts the number of edges matching the provided pattern. + * This method includes optimizations for common cases, such as counting all edges + * or counting edges with a specific predicate, to avoid full iteration when possible. + * + * @param pattern the pattern defining the edges to count. + * @return the number of matching edges. + * @throws DataManagerException if the counting operation fails. + */ @Override public long count(EdgePattern pattern) throws DataManagerException { if (pattern == null) { @@ -86,21 +103,21 @@ public long count(EdgePattern pattern) throws DataManagerException { pattern.isObject() && pattern.isContexts()) { int count = graph.size(pattern.getPredicate()); - logger.debug("Count (optimized): {}", count); + logger.debug("Count (optimized by predicate): {}", count); return count; } - // Optimize: if no constraints, use Graph.size() + // Optimization: if the pattern matches everything, use the total graph size if (pattern.matchesAll()) { int count = graph.size(); - logger.debug("Count (total): {}", count); + logger.debug("Count (total graph size): {}", count); return count; } - // General case: count by iterating + // General case: perform a query and count the stream elements try (Stream stream = query(pattern)) { long count = stream.count(); - logger.debug("Count (general): {}", count); + logger.debug("Count (general iteration): {}", count); return count; } @@ -116,6 +133,13 @@ public long count(EdgePattern pattern) throws DataManagerException { } } + /** + * Checks if at least one edge matches the provided pattern. + * + * @param pattern the pattern defining the search criteria. + * @return {@code true} if at least one matching edge exists, {@code false} otherwise. + * @throws DataManagerException if the existence check fails. + */ @Override public boolean exists(EdgePattern pattern) throws DataManagerException { if (pattern == null) { @@ -125,7 +149,7 @@ public boolean exists(EdgePattern pattern) throws DataManagerException { try { logger.debug("Checking existence with pattern: {}", pattern); - // Optimize: use findFirst() which stops after first match + // Use findFirst() to terminate iteration immediately upon finding a match try (Stream stream = query(pattern)) { boolean exists = stream.findFirst().isPresent(); logger.debug("Exists: {}", exists); @@ -144,6 +168,13 @@ public boolean exists(EdgePattern pattern) throws DataManagerException { } } + /** + * Attempts to find a specific edge instance within the graph. + * + * @param edge the edge template or reference to look for. + * @return the found {@link Edge} instance from the graph, or the original edge if not found but valid. + * @throws DataManagerException if the lookup fails. + */ @Override public Edge find(Edge edge) throws DataManagerException { if (edge == null) { @@ -153,10 +184,10 @@ public Edge find(Edge edge) throws DataManagerException { try { logger.debug("Finding edge: {}", edge); - // Use Graph's find method for RDF-star support + // Use Graph's find method which supports advanced features like RDF-star Edge found = graph.find(edge); - logger.debug("Found: {}", found); + logger.debug("Found result: {}", found); return found != null ? found : edge; } catch (Exception e) { diff --git a/src/main/java/fr/inria/corese/core/storage/impl/transaction/TransactionImpl.java b/src/main/java/fr/inria/corese/core/storage/impl/transaction/TransactionImpl.java index 5fa5b1f6d..5d83f9e5e 100644 --- a/src/main/java/fr/inria/corese/core/storage/impl/transaction/TransactionImpl.java +++ b/src/main/java/fr/inria/corese/core/storage/impl/transaction/TransactionImpl.java @@ -24,23 +24,38 @@ public class TransactionImpl implements Transaction { private final Instant startTime; private final TransactionCallback callback; + /** + * Thread-safe reference to the current state of the transaction. + */ private final AtomicReference state; /** * Callback interface for transaction operations. - * Allows the TransactionManager to be notified of commits/rollbacks. + * Allows the TransactionManager or DataManager to be notified of lifecycle changes. */ public interface TransactionCallback { + /** + * Invoked when the transaction is requested to commit. + * + * @param transaction the transaction being committed. + * @throws DataManagerException if the commit operation fails. + */ void onCommit(TransactionImpl transaction) throws DataManagerException; + /** + * Invoked when the transaction is requested to rollback. + * + * @param transaction the transaction being rolled back. + * @throws DataManagerException if the rollback operation fails. + */ void onRollback(TransactionImpl transaction) throws DataManagerException; } /** - * Constructs a new transaction. + * Constructs a new transaction handle. * - * @param isolationLevel Isolation level - * @param callback Callback for commit/rollback operations + * @param isolationLevel the requested isolation level for this transaction. + * @param callback the callback to handle persistence of changes. */ public TransactionImpl(IsolationLevel isolationLevel, TransactionCallback callback) { this.id = UUID.randomUUID().toString(); @@ -62,7 +77,12 @@ public IsolationLevel getIsolationLevel() { return isolationLevel; } - + /** + * Commits the changes made during this transaction. + * + * @throws IllegalStateException if the transaction is not in an ACTIVE state. + * @throws DataManagerException if the commit fails at the storage level. + */ @Override public void commit() throws DataManagerException { TransactionState currentState = state.get(); @@ -76,20 +96,19 @@ public void commit() throws DataManagerException { logger.debug("Committing transaction {}", id); try { - // Notify callback + // Execute the commit logic via the registered callback callback.onCommit(this); - // Update state + // Atomically update state to COMMITTED if (!state.compareAndSet(TransactionState.ACTIVE, TransactionState.COMMITTED)) { throw new IllegalStateException( - "Transaction state changed during commit" + "Transaction state changed during commit process" ); } logger.info("Transaction {} committed successfully", id); } catch (DataManagerException e) { - // Mark as failed state.set(TransactionState.FAILED); logger.error("Failed to commit transaction {}", id, e); throw e; @@ -105,6 +124,12 @@ public void commit() throws DataManagerException { } } + /** + * Reverts the changes made during this transaction. + * + * @throws IllegalStateException if the transaction is not in an ACTIVE state. + * @throws DataManagerException if the rollback operation fails. + */ @Override public void rollback() throws DataManagerException { TransactionState currentState = state.get(); @@ -118,13 +143,13 @@ public void rollback() throws DataManagerException { logger.debug("Rolling back transaction {}", id); try { - // Notify callback + // Execute the rollback logic via the registered callback callback.onRollback(this); - // Update state + // Atomically update state to ROLLED_BACK if (!state.compareAndSet(TransactionState.ACTIVE, TransactionState.ROLLED_BACK)) { throw new IllegalStateException( - "Transaction state changed during rollback" + "Transaction state changed during rollback process" ); } @@ -156,16 +181,21 @@ public TransactionState getState() { return state.get(); } + /** + * Closes the transaction. If the transaction is still active, an automatic rollback is performed. + * + * @throws DataManagerException if an automatic rollback fails. + */ @Override public void close() throws DataManagerException { TransactionState currentState = state.get(); if (currentState.isActive()) { - logger.warn("Transaction {} not committed, performing automatic rollback", id); + logger.warn("Transaction {} closed while still active; performing automatic rollback", id); try { rollback(); } catch (DataManagerException e) { - logger.error("Failed to auto-rollback transaction {}", id, e); + logger.error("Failed to perform auto-rollback for transaction {}", id, e); throw e; } } diff --git a/src/main/java/fr/inria/corese/core/storage/impl/transaction/TransactionManagerImpl.java b/src/main/java/fr/inria/corese/core/storage/impl/transaction/TransactionManagerImpl.java index ada4b50f0..ef74416ce 100644 --- a/src/main/java/fr/inria/corese/core/storage/impl/transaction/TransactionManagerImpl.java +++ b/src/main/java/fr/inria/corese/core/storage/impl/transaction/TransactionManagerImpl.java @@ -24,15 +24,18 @@ public class TransactionManagerImpl implements TransactionManager { private final boolean transactionSupport; private final IsolationLevel defaultIsolationLevel; - // Thread-local storage for current transaction per thread + /** + * Thread-local storage to track the current active transaction handle for the calling thread. + */ private final ThreadLocal currentTransaction = new ThreadLocal<>(); /** - * Constructs a transaction manager. + * Constructs a new TransactionManager. * - * @param graph Graph to manage - * @param transactionSupport Whether transactions are enabled - * @param defaultIsolationLevel Default isolation level + * @param graph the underlying Corese Graph. + * @param transactionSupport flag indicating if transaction features are enabled. + * @param defaultIsolationLevel the isolation level to use when none is specified. + * @throws IllegalArgumentException if graph or defaultIsolationLevel is null. */ public TransactionManagerImpl( Graph graph, @@ -67,7 +70,7 @@ public Transaction beginTransaction() throws DataManagerException { @Override public Transaction beginTransaction(IsolationLevel isolationLevel) throws DataManagerException { if (!supportsTransactions()) { - throw new UnsupportedOperationException("Transactions are not supported"); + throw new UnsupportedOperationException("Transactions are not supported by this manager configuration."); } if (isolationLevel == null) { @@ -76,20 +79,18 @@ public Transaction beginTransaction(IsolationLevel isolationLevel) throws DataMa if (!getSupportedIsolationLevels().contains(isolationLevel)) { throw new IllegalArgumentException( - "Isolation level " + isolationLevel + " is not supported" + "Isolation level " + isolationLevel + " is not supported by the current storage backend." ); } - // Check if there's already an active transaction - if (hasActiveTransaction()) { - TransactionImpl existing = currentTransaction.get(); - logger.warn("Starting new transaction while transaction {} is still active", + // Check for existing active transaction to prevent unsupported nesting + TransactionImpl existing = currentTransaction.get(); + if (existing != null && existing.isActive()) { + logger.warn("Attempted to start a new transaction while transaction {} is still active.", existing.getId()); - // For now, we don't support nested transactions throw new DataManagerException( ErrorCode.TRANSACTION_ERROR, - "Nested transactions are not supported. " + - "Current transaction: " + existing.getId() + "Nested transactions are not supported. Current active transaction: " + existing.getId() ); } @@ -105,7 +106,7 @@ public void onRollback(TransactionImpl transaction) throws DataManagerException } }; - // Create and register transaction + // Create the new transaction handle and register it to the current thread TransactionImpl transaction = new TransactionImpl(isolationLevel, callback); currentTransaction.set(transaction); @@ -127,17 +128,16 @@ public IsolationLevel getDefaultIsolationLevel() { @Override public Set getSupportedIsolationLevels() { - // For now, we support all isolation levels - // In a real implementation, this would depend on the backend storage + // Corese currently supports standard levels, but this may be restricted by specific backends return EnumSet.allOf(IsolationLevel.class); } /** - * Performs the actual commit operation. - * Called by the transaction when commit() is invoked. + * Executes the internal commit logic for a transaction. + * This method is triggered by the {@link TransactionImpl#commit()} method via the callback. * - * @param transaction Transaction to commit - * @throws DataManagerException if commit fails + * @param transaction the transaction to commit. + * @throws DataManagerException if the graph commit fails. */ private void performCommit(TransactionImpl transaction) throws DataManagerException { try { @@ -145,7 +145,7 @@ private void performCommit(TransactionImpl transaction) throws DataManagerExcept graph.init(); - // Clear thread-local + // Clear the thread-local reference as the transaction lifecycle is ending currentTransaction.remove(); logger.debug("Commit completed for transaction {}", transaction.getId()); @@ -154,26 +154,27 @@ private void performCommit(TransactionImpl transaction) throws DataManagerExcept logger.error("Failed to commit transaction {}", transaction.getId(), e); throw new DataManagerException( ErrorCode.TRANSACTION_ERROR, - "Failed to commit transaction: " + e.getMessage(), + "Failed to commit transaction changes to the graph: " + e.getMessage(), e ); } } /** - * Performs the actual rollback operation. - * Called by the transaction when rollback() is invoked. + * Executes the internal rollback logic for a transaction. + * This method is triggered by the {@link TransactionImpl#rollback()} method via the callback. * - * @param transaction Transaction to rollback - * @throws DataManagerException if rollback fails + * @param transaction the transaction to roll back. + * @throws DataManagerException if the rollback operation fails. */ private void performRollback(TransactionImpl transaction) throws DataManagerException { try { logger.debug("Performing rollback for transaction {}", transaction.getId()); + // Placeholder for rolling back volatile or buffered changes in the graph graph.init(); - // Clear thread-local + // Clear the thread-local reference currentTransaction.remove(); logger.debug("Rollback completed for transaction {}", transaction.getId()); @@ -182,7 +183,7 @@ private void performRollback(TransactionImpl transaction) throws DataManagerExce logger.error("Failed to rollback transaction {}", transaction.getId(), e); throw new DataManagerException( ErrorCode.TRANSACTION_ERROR, - "Failed to rollback transaction: " + e.getMessage(), + "Failed to rollback transaction changes: " + e.getMessage(), e ); } diff --git a/src/test/java/fr/inria/corese/core/query/QueryEngineTest.java b/src/test/java/fr/inria/corese/core/query/QueryEngineTest.java index eab91369f..8f198c8e0 100644 --- a/src/test/java/fr/inria/corese/core/query/QueryEngineTest.java +++ b/src/test/java/fr/inria/corese/core/query/QueryEngineTest.java @@ -181,6 +181,7 @@ void testClearCompilationCache() throws EngineException { } @Test + @SuppressWarnings("unchecked") void testCleanMethod() { ArrayList internalList = null; try { diff --git a/src/test/java/fr/inria/corese/core/storage/impl/lifecycle/LifecycleManagerImplTest.java b/src/test/java/fr/inria/corese/core/storage/impl/lifecycle/LifecycleManagerImplTest.java new file mode 100644 index 000000000..e38ffbfeb --- /dev/null +++ b/src/test/java/fr/inria/corese/core/storage/impl/lifecycle/LifecycleManagerImplTest.java @@ -0,0 +1,175 @@ +package fr.inria.corese.core.storage.impl.lifecycle; + +import fr.inria.corese.core.Graph; +import fr.inria.corese.core.storage.api.dataManager.lifecycle.LifecycleState; +import fr.inria.corese.core.storage.api.dataManager.support.config.DataManagerConfig; +import fr.inria.corese.core.storage.api.dataManager.support.exception.DataManagerException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Unit tests for LifecycleManagerImpl. + */ +@DisplayName("LifecycleManagerImpl Tests") +class LifecycleManagerImplTest { + + private LifecycleManagerImpl lifecycleManager; + + @BeforeEach + void setUp() { + Graph graph = new Graph(); + lifecycleManager = new LifecycleManagerImpl(graph); + } + + @Test + @DisplayName("Initial state should be NOT_INITIALIZED") + void testInitialStateIsNotInitialized() { + assertEquals(LifecycleState.NOT_INITIALIZED, lifecycleManager.getState()); + assertFalse(lifecycleManager.isInitialized()); + } + + @Test + @DisplayName("Initialize with valid config should succeed") + void testInitializeSuccess() throws DataManagerException { + DataManagerConfig config = DataManagerConfig.builder() + .debug(true) + .build(); + + lifecycleManager.initialize(config); + + assertEquals(LifecycleState.RUNNING, lifecycleManager.getState()); + assertTrue(lifecycleManager.isInitialized()); + } + + @Test + @DisplayName("Initialize with null config should throw IllegalArgumentException") + void testInitializeWithNullConfigThrows() { + assertThrows(IllegalArgumentException.class, () -> lifecycleManager.initialize(null)); + } + + + @Test + @DisplayName("Shutdown after initialization should succeed") + void testShutdownSuccess() throws DataManagerException { + DataManagerConfig config = DataManagerConfig.builder().build(); + lifecycleManager.initialize(config); + + lifecycleManager.shutdown(); + + assertEquals(LifecycleState.SHUTDOWN, lifecycleManager.getState()); + assertFalse(lifecycleManager.isInitialized()); + } + + + @Test + @DisplayName("Restart should reinitialize successfully") + void testRestartSuccess() throws DataManagerException { + DataManagerConfig config1 = DataManagerConfig.builder().debug(false).build(); + lifecycleManager.initialize(config1); + + DataManagerConfig config2 = DataManagerConfig.builder().debug(true).build(); + lifecycleManager.restart(config2); + + assertEquals(LifecycleState.RUNNING, lifecycleManager.getState()); + assertTrue(lifecycleManager.isInitialized()); + } + + @Test + @DisplayName("checkUsable when RUNNING should not throw") + void testCheckUsableWhenRunning() throws DataManagerException { + DataManagerConfig config = DataManagerConfig.builder().build(); + lifecycleManager.initialize(config); + + // Should not throw + assertDoesNotThrow(() -> lifecycleManager.checkUsable()); + } + + @Test + @DisplayName("checkUsable when NOT_INITIALIZED should throw IllegalStateException") + void testCheckUsableWhenNotInitialized() { + assertThrows(IllegalStateException.class, () -> lifecycleManager.checkUsable()); + } + + @Test + @DisplayName("checkUsable after shutdown should throw IllegalStateException") + void testCheckUsableAfterShutdown() throws DataManagerException { + DataManagerConfig config = DataManagerConfig.builder().build(); + lifecycleManager.initialize(config); + lifecycleManager.shutdown(); + + assertThrows(IllegalStateException.class, () -> lifecycleManager.checkUsable()); + } + + @Test + @DisplayName("Initialize with custom storage path") + void testInitializeWithCustomStoragePath() throws DataManagerException { + String customPath = "http://example.org/custom/storage"; + DataManagerConfig config = DataManagerConfig.builder() + .storagePath(customPath) + .build(); + + lifecycleManager.initialize(config); + + assertTrue(lifecycleManager.isInitialized()); + assertEquals(LifecycleState.RUNNING, lifecycleManager.getState()); + } + + @Test + @DisplayName("Initialize with transaction support enabled") + void testInitializeWithTransactionSupport() throws DataManagerException { + DataManagerConfig config = DataManagerConfig.builder() + .transactionSupport(true) + .build(); + + lifecycleManager.initialize(config); + + assertTrue(lifecycleManager.isInitialized()); + } + + @Test + @DisplayName("Initialize with custom properties") + void testInitializeWithCustomProperties() throws DataManagerException { + DataManagerConfig config = DataManagerConfig.builder() + .property("custom.key", "custom.value") + .property("another.key", "another.value") + .build(); + + lifecycleManager.initialize(config); + + assertTrue(lifecycleManager.isInitialized()); + assertEquals(LifecycleState.RUNNING, lifecycleManager.getState()); + } + + @Test + @DisplayName("Multiple restarts should work correctly") + void testMultipleRestarts() throws DataManagerException { + DataManagerConfig config1 = DataManagerConfig.builder().debug(false).build(); + lifecycleManager.initialize(config1); + + for (int i = 0; i < 3; i++) { + DataManagerConfig config = DataManagerConfig.builder() + .debug(i % 2 == 0) + .build(); + lifecycleManager.restart(config); + + assertEquals(LifecycleState.RUNNING, lifecycleManager.getState()); + assertTrue(lifecycleManager.isInitialized()); + } + } + + @Test + @DisplayName("State transitions should be correct") + void testStateTransitions() throws DataManagerException { + assertEquals(LifecycleState.NOT_INITIALIZED, lifecycleManager.getState()); + + DataManagerConfig config = DataManagerConfig.builder().build(); + lifecycleManager.initialize(config); + assertEquals(LifecycleState.RUNNING, lifecycleManager.getState()); + + lifecycleManager.shutdown(); + assertEquals(LifecycleState.SHUTDOWN, lifecycleManager.getState()); + } +} \ No newline at end of file diff --git a/src/test/java/fr/inria/corese/core/storage/impl/operations/BulkOperationsImplTest.java b/src/test/java/fr/inria/corese/core/storage/impl/operations/BulkOperationsImplTest.java new file mode 100644 index 000000000..147dd2af9 --- /dev/null +++ b/src/test/java/fr/inria/corese/core/storage/impl/operations/BulkOperationsImplTest.java @@ -0,0 +1,226 @@ +package fr.inria.corese.core.storage.impl.operations; + +import fr.inria.corese.core.Graph; +import fr.inria.corese.core.kgram.api.core.Edge; +import fr.inria.corese.core.kgram.api.core.Node; +import fr.inria.corese.core.storage.api.dataManager.support.exception.DataManagerException; +import fr.inria.corese.core.storage.api.dataManager.support.model.MutationResult; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Unit tests for BulkOperationsImpl. + */ +@DisplayName("BulkOperationsImpl Tests") +class BulkOperationsImplTest { + + private BulkOperationsImpl bulkOperations; + private Graph graph; + + @BeforeEach + void setUp() { + graph = new Graph(); + bulkOperations = new BulkOperationsImpl(graph); + } + + @Test + @DisplayName("Constructor with null graph should throw IllegalArgumentException") + void testConstructorWithNullGraphThrows() { + assertThrows(IllegalArgumentException.class, () -> new BulkOperationsImpl(null)); + } + + @Test + @DisplayName("Insert batch of 5 edges should succeed") + void testInsertBatchSuccess() throws DataManagerException { + List edges = new ArrayList<>(); + for (int i = 0; i < 5; i++) { + Node s = graph.addResource("http://example.org/s" + i); + Node p = graph.addProperty("http://example.org/p"); + Node o = graph.addResource("http://example.org/o" + i); + edges.add(graph.create(graph.getDefaultGraphNode(), s, p, o)); + } + + MutationResult result = bulkOperations.insertBatch(edges); + + assertTrue(result.isBulk()); + assertEquals(5, result.getTotalAttempted()); + assertEquals(5, result.getSuccessCount()); + assertEquals(0, result.getFailureCount()); + assertEquals(5, graph.size()); + } + + @Test + @DisplayName("Insert batch with empty list should throw IllegalArgumentException") + void testInsertBatchWithEmptyListThrows() { + assertThrows(IllegalArgumentException.class, () -> bulkOperations.insertBatch(new ArrayList<>())); + } + + @Test + @DisplayName("Insert batch with null list should throw IllegalArgumentException") + void testInsertBatchWithNullListThrows() { + assertThrows(IllegalArgumentException.class, () -> bulkOperations.insertBatch(null)); + } + + @Test + @DisplayName("Delete batch of 3 edges should succeed") + void testDeleteBatchSuccess() throws DataManagerException { + List edges = new ArrayList<>(); + for (int i = 0; i < 3; i++) { + Node s = graph.addResource("http://example.org/s" + i); + Node p = graph.addProperty("http://example.org/p"); + Node o = graph.addResource("http://example.org/o" + i); + edges.add(graph.addEdge(s, p, o)); + } + + MutationResult result = bulkOperations.deleteBatch(edges); + + assertTrue(result.isBulk()); + assertEquals(3, result.getSuccessCount()); + assertEquals(0, graph.size()); + } + + + @Test + @DisplayName("Clear all should remove all edges from graph") + void testClearAll() throws DataManagerException { + // Add some data + for (int i = 0; i < 10; i++) { + Node s = graph.addResource("http://example.org/s" + i); + Node p = graph.addProperty("http://example.org/p"); + Node o = graph.addResource("http://example.org/o" + i); + graph.addEdge(s, p, o); + } + + MutationResult result = bulkOperations.clearAll(); + + assertTrue(result.isBulk()); + assertEquals(0, graph.size()); + } + + @Test + @DisplayName("Declare context should succeed") + void testDeclareContext() throws DataManagerException { + Node context = graph.addResource("http://example.org/context"); + + MutationResult result = bulkOperations.declareContext(context); + + assertTrue(result.isSuccess()); + } + + @Test + @DisplayName("Declare context with null should throw IllegalArgumentException") + void testDeclareContextWithNullThrows() { + assertThrows(IllegalArgumentException.class, () -> bulkOperations.declareContext(null)); + } + + @Test + @DisplayName("Undeclare context should remove context and its edges") + void testUndeclareContext() throws DataManagerException { + Node context = graph.addResource("http://example.org/context"); + Node s = graph.addResource("http://example.org/subject"); + Node p = graph.addProperty("http://example.org/predicate"); + Node o = graph.addResource("http://example.org/object"); + + graph.addEdge(s, p, o, context); + + MutationResult result = bulkOperations.undeclareContext(context); + + assertTrue(result.isBulk()); + } + + @Test + @DisplayName("Insert batch with partial failure should track failures") + void testInsertBatchPartialFailure() throws DataManagerException { + List edges = new ArrayList<>(); + + // Valid edge + Node s1 = graph.addResource("http://example.org/s1"); + Node p = graph.addProperty("http://example.org/p"); + Node o1 = graph.addResource("http://example.org/o1"); + edges.add(graph.create(graph.getDefaultGraphNode(), s1, p, o1)); + + graph.addEdge(s1, p, o1); + + MutationResult result = bulkOperations.insertBatch(edges); + + assertEquals(1, result.getTotalAttempted()); + } + + @Test + @DisplayName("Success rate should be correctly calculated") + void testGetSuccessRate() throws DataManagerException { + List edges = new ArrayList<>(); + for (int i = 0; i < 10; i++) { + Node s = graph.addResource("http://example.org/s" + i); + Node p = graph.addProperty("http://example.org/p"); + Node o = graph.addResource("http://example.org/o" + i); + edges.add(graph.create(graph.getDefaultGraphNode(), s, p, o)); + } + + MutationResult result = bulkOperations.insertBatch(edges); + + assertEquals(1.0, result.getSuccessRate(), 0.001); + } + + @Test + @DisplayName("Large batch insert (100 edges) should succeed") + void testLargeBatchInsert() throws DataManagerException { + List edges = new ArrayList<>(); + for (int i = 0; i < 100; i++) { + Node s = graph.addResource("http://example.org/s" + i); + Node p = graph.addProperty("http://example.org/p"); + Node o = graph.addResource("http://example.org/o" + i); + edges.add(graph.create(graph.getDefaultGraphNode(), s, p, o)); + } + + MutationResult result = bulkOperations.insertBatch(edges); + + assertTrue(result.isCompleteSuccess()); + assertEquals(100, result.getSuccessCount()); + assertEquals(100, graph.size()); + } + + + @Test + @DisplayName("Clear all on empty graph should succeed with no deletions") + void testClearAllOnEmptyGraph() throws DataManagerException { + MutationResult result = bulkOperations.clearAll(); + + assertTrue(result.isBulk()); + assertEquals(0, graph.size()); + } + + @Test + @DisplayName("Multiple batch operations should all succeed") + void testMultipleBatchOperations() throws DataManagerException { + // First batch + List batch1 = new ArrayList<>(); + for (int i = 0; i < 5; i++) { + Node s = graph.addResource("http://example.org/s" + i); + Node p = graph.addProperty("http://example.org/p1"); + Node o = graph.addResource("http://example.org/o" + i); + batch1.add(graph.create(graph.getDefaultGraphNode(), s, p, o)); + } + bulkOperations.insertBatch(batch1); + + assertEquals(5, graph.size()); + + // Second batch + List batch2 = new ArrayList<>(); + for (int i = 5; i < 10; i++) { + Node s = graph.addResource("http://example.org/s" + i); + Node p = graph.addProperty("http://example.org/p2"); + Node o = graph.addResource("http://example.org/o" + i); + batch2.add(graph.create(graph.getDefaultGraphNode(), s, p, o)); + } + bulkOperations.insertBatch(batch2); + + assertEquals(10, graph.size()); + } +} \ No newline at end of file From f87587597b52474e6f9774751369008a7f629f7b Mon Sep 17 00:00:00 2001 From: "AD\\aabdoun" Date: Mon, 12 Jan 2026 16:11:06 +0100 Subject: [PATCH 4/8] test unitaire MutationOperationsImpl --- .../MutationOperationsImplTest.java | 288 ++++++++++++++++++ 1 file changed, 288 insertions(+) create mode 100644 src/test/java/fr/inria/corese/core/storage/impl/operations/MutationOperationsImplTest.java diff --git a/src/test/java/fr/inria/corese/core/storage/impl/operations/MutationOperationsImplTest.java b/src/test/java/fr/inria/corese/core/storage/impl/operations/MutationOperationsImplTest.java new file mode 100644 index 000000000..ad71abde6 --- /dev/null +++ b/src/test/java/fr/inria/corese/core/storage/impl/operations/MutationOperationsImplTest.java @@ -0,0 +1,288 @@ +package fr.inria.corese.core.storage.impl.operations; + +import fr.inria.corese.core.Graph; +import fr.inria.corese.core.kgram.api.core.Edge; +import fr.inria.corese.core.kgram.api.core.Node; +import fr.inria.corese.core.storage.api.dataManager.support.exception.DataManagerException; +import fr.inria.corese.core.storage.api.dataManager.support.model.MutationResult; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Unit tests for MutationOperationsImpl. + * + */ +@DisplayName("MutationOperationsImpl Tests") +class MutationOperationsImplTest { + + private MutationOperationsImpl mutationOperations; + private Graph graph; + + @BeforeEach + void setUp() { + graph = new Graph(); + mutationOperations = new MutationOperationsImpl(graph); + } + + @Test + @DisplayName("Constructor with null graph should throw IllegalArgumentException") + void testConstructorWithNullGraphThrows() { + assertThrows(IllegalArgumentException.class, () -> new MutationOperationsImpl(null)); + } + + @Test + @DisplayName("Insert edge should succeed and return success result") + void testInsertEdgeSuccess() throws DataManagerException { + Node s = graph.addResource("http://example.org/subject"); + Node p = graph.addProperty("http://example.org/predicate"); + Node o = graph.addResource("http://example.org/object"); + Edge edge = graph.create(graph.getDefaultGraphNode(), s, p, o); + + MutationResult result = mutationOperations.insertEdge(edge); + + assertTrue(result.isSuccess()); + assertNotNull(result.getAffectedEdge()); + assertEquals(1, graph.size()); + } + + @Test + @DisplayName("Insert edge with null should throw IllegalArgumentException") + void testInsertEdgeWithNullThrows() { + assertThrows(IllegalArgumentException.class, () -> mutationOperations.insertEdge(null)); + } + + @Test + @DisplayName("Insert edge with pattern (s, p, o) should succeed") + void testInsertEdgeWithPattern() throws DataManagerException { + Node s = graph.addResource("http://example.org/subject"); + Node p = graph.addProperty("http://example.org/predicate"); + Node o = graph.addResource("http://example.org/object"); + + MutationResult result = mutationOperations.insertEdge(s, p, o, null); + + assertTrue(result.isSuccess()); + assertEquals(1, graph.size()); + } + + @Test + @DisplayName("Insert edge with null subject should throw IllegalArgumentException") + void testInsertEdgeWithNullSubjectThrows() { + Node p = graph.addProperty("http://example.org/predicate"); + Node o = graph.addResource("http://example.org/object"); + + assertThrows(IllegalArgumentException.class, () -> mutationOperations.insertEdge(null, p, o, null)); + } + + @Test + @DisplayName("Insert edge with null predicate should throw IllegalArgumentException") + void testInsertEdgeWithNullPredicateThrows() { + Node s = graph.addResource("http://example.org/subject"); + Node o = graph.addResource("http://example.org/object"); + + assertThrows(IllegalArgumentException.class, () -> mutationOperations.insertEdge(s, null, o, null)); + } + + @Test + @DisplayName("Insert edge with null object should throw IllegalArgumentException") + void testInsertEdgeWithNullObjectThrows() { + Node s = graph.addResource("http://example.org/subject"); + Node p = graph.addProperty("http://example.org/predicate"); + + assertThrows(IllegalArgumentException.class, () -> mutationOperations.insertEdge(s, p, null, null)); + } + + @Test + @DisplayName("Delete edge should succeed and return success result") + void testDeleteEdgeSuccess() throws DataManagerException { + Node s = graph.addResource("http://example.org/subject"); + Node p = graph.addProperty("http://example.org/predicate"); + Node o = graph.addResource("http://example.org/object"); + + // Setup: insert edge using graph directly + graph.addEdge(s, p, o); + + // Create edge reference for deletion + Edge edge = graph.create(graph.getDefaultGraphNode(), s, p, o); + + MutationResult result = mutationOperations.deleteEdge(edge); + + assertTrue(result.isSuccess()); + assertEquals(0, graph.size()); + } + + @Test + @DisplayName("Delete edge with null should throw IllegalArgumentException") + void testDeleteEdgeWithNullThrows() { + assertThrows(IllegalArgumentException.class, () -> mutationOperations.deleteEdge(null)); + } + + @Test + @DisplayName("Delete non-existent edge should return failure result") + void testDeleteNonExistentEdge() throws DataManagerException { + Node s = graph.addResource("http://example.org/subject"); + Node p = graph.addProperty("http://example.org/predicate"); + Node o = graph.addResource("http://example.org/object"); + Edge edge = graph.create(graph.getDefaultGraphNode(), s, p, o); + + MutationResult result = mutationOperations.deleteEdge(edge); + + assertTrue(result.isFailure()); + } + + @Test + @DisplayName("Generate blank node should return unique identifier") + void testGenerateBlankNode() throws DataManagerException { + String blankId1 = mutationOperations.generateBlankNode(); + String blankId2 = mutationOperations.generateBlankNode(); + + assertNotNull(blankId1); + assertNotNull(blankId2); + assertTrue(blankId1.startsWith("_:")); + assertTrue(blankId2.startsWith("_:")); + assertNotEquals(blankId1, blankId2); + } + + @Test + @DisplayName("Update edge should succeed") + void testUpdateEdge() throws DataManagerException { + Node s = graph.addResource("http://example.org/subject"); + Node p = graph.addProperty("http://example.org/predicate"); + Node o1 = graph.addResource("http://example.org/object1"); + Node o2 = graph.addResource("http://example.org/object2"); + + // Setup: add old edge + graph.addEdge(s, p, o1); + Edge oldEdge = graph.create(graph.getDefaultGraphNode(), s, p, o1); + Edge newEdge = graph.create(graph.getDefaultGraphNode(), s, p, o2); + + MutationResult result = mutationOperations.updateEdge(oldEdge, newEdge); + + assertTrue(result.isSuccess()); + assertEquals(1, graph.size()); + } + + @Test + @DisplayName("Update edge with null old edge should throw IllegalArgumentException") + void testUpdateEdgeWithNullOldEdgeThrows() { + Node s = graph.addResource("http://example.org/subject"); + Node p = graph.addProperty("http://example.org/predicate"); + Node o = graph.addResource("http://example.org/object"); + Edge newEdge = graph.create(graph.getDefaultGraphNode(), s, p, o); + + assertThrows(IllegalArgumentException.class, () -> mutationOperations.updateEdge(null, newEdge)); + } + + @Test + @DisplayName("Update edge with null new edge should throw IllegalArgumentException") + void testUpdateEdgeWithNullNewEdgeThrows() { + Node s = graph.addResource("http://example.org/subject"); + Node p = graph.addProperty("http://example.org/predicate"); + Node o = graph.addResource("http://example.org/object"); + + // Setup + graph.addEdge(s, p, o); + Edge oldEdge = graph.create(graph.getDefaultGraphNode(), s, p, o); + + assertThrows(IllegalArgumentException.class, () -> mutationOperations.updateEdge(oldEdge, null)); + } + + @Test + @DisplayName("Insert edge with specific context should use that context") + void testInsertEdgeWithContext() throws DataManagerException { + Node context = graph.addGraph("http://example.org/myGraph"); + Node s = graph.addResource("http://example.org/subject"); + Node p = graph.addProperty("http://example.org/predicate"); + Node o = graph.addResource("http://example.org/object"); + + MutationResult result = mutationOperations.insertEdge(s, p, o, List.of(context)); + + assertTrue(result.isSuccess()); + assertEquals(1, graph.size()); + } + + @Test + @DisplayName("Insert duplicate edge should handle gracefully") + void testInsertDuplicateEdge() throws DataManagerException { + Node s = graph.addResource("http://example.org/subject"); + Node p = graph.addProperty("http://example.org/predicate"); + Node o = graph.addResource("http://example.org/object"); + + // Insert first time + MutationResult result1 = mutationOperations.insertEdge(s, p, o, null); + assertTrue(result1.isSuccess()); + + // Insert duplicate + MutationResult result2 = mutationOperations.insertEdge(s, p, o, null); + + // May succeed or fail depending on Graph implementation + assertNotNull(result2); + } + + @Test + @DisplayName("Delete with pattern and specific context should delete from that context only") + void testDeleteEdgesWithContext() throws DataManagerException { + Node ctx1 = graph.addGraph("http://example.org/graph1"); + Node ctx2 = graph.addGraph("http://example.org/graph2"); + Node s = graph.addResource("http://example.org/subject"); + Node p = graph.addProperty("http://example.org/predicate"); + Node o = graph.addResource("http://example.org/object"); + + // Setup: add edges to both contexts + graph.addEdge(ctx1, s, p, o); + graph.addEdge(ctx2, s, p, o); + + MutationResult result = mutationOperations.deleteEdges(s, p, o, List.of(ctx1)); + + assertTrue(result.isBulk()); + assertTrue(result.getSuccessCount() >= 1); + } + + @Test + @DisplayName("MutationResult should contain correct edge information") + void testMutationResultContainsEdgeInfo() throws DataManagerException { + Node s = graph.addResource("http://example.org/subject"); + Node p = graph.addProperty("http://example.org/predicate"); + Node o = graph.addResource("http://example.org/object"); + Edge edge = graph.create(graph.getDefaultGraphNode(), s, p, o); + + MutationResult result = mutationOperations.insertEdge(edge); + + assertTrue(result.isSuccess()); + assertNotNull(result.getAffectedEdge()); + assertEquals(edge.getSubjectNode(), result.getAffectedEdge().getSubjectNode()); + assertEquals(edge.getPropertyNode(), result.getAffectedEdge().getPropertyNode()); + assertEquals(edge.getObjectNode(), result.getAffectedEdge().getObjectNode()); + } + + @Test + @DisplayName("Multiple inserts should accumulate in graph") + void testMultipleInserts() throws DataManagerException { + for (int i = 0; i < 5; i++) { + Node s = graph.addResource("http://example.org/s" + i); + Node p = graph.addProperty("http://example.org/p"); + Node o = graph.addResource("http://example.org/o" + i); + + MutationResult result = mutationOperations.insertEdge(s, p, o, null); + assertTrue(result.isSuccess()); + } + + assertEquals(5, graph.size()); + } + + @Test + @DisplayName("Delete pattern with no matches should return empty result") + void testDeletePatternNoMatches() throws DataManagerException { + Node s = graph.addResource("http://example.org/subject"); + Node p = graph.addProperty("http://example.org/predicate"); + + MutationResult result = mutationOperations.deleteEdges(s, p, null, null); + + assertTrue(result.isBulk()); + assertEquals(0, result.getSuccessCount()); + } +} \ No newline at end of file From 7e58d621974504dd172259247919dcaf9c7fa5b5 Mon Sep 17 00:00:00 2001 From: "AD\\aabdoun" Date: Tue, 13 Jan 2026 10:02:26 +0100 Subject: [PATCH 5/8] test unitaire CoreseGraphDataManager --- ...CoreseGraphDataManagerIntegrationTest.java | 136 ++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 src/test/java/fr/inria/corese/core/storage/CoreseGraphDataManagerIntegrationTest.java diff --git a/src/test/java/fr/inria/corese/core/storage/CoreseGraphDataManagerIntegrationTest.java b/src/test/java/fr/inria/corese/core/storage/CoreseGraphDataManagerIntegrationTest.java new file mode 100644 index 000000000..0b613ecca --- /dev/null +++ b/src/test/java/fr/inria/corese/core/storage/CoreseGraphDataManagerIntegrationTest.java @@ -0,0 +1,136 @@ +package fr.inria.corese.core.storage; + +import fr.inria.corese.core.kgram.api.core.Edge; +import fr.inria.corese.core.kgram.api.core.Node; +import fr.inria.corese.core.storage.api.dataManager.support.config.DataManagerConfig; +import fr.inria.corese.core.storage.api.dataManager.support.exception.DataManagerException; +import fr.inria.corese.core.storage.api.dataManager.support.model.EdgePattern; +import fr.inria.corese.core.storage.api.dataManager.support.model.GraphStatistics; +import fr.inria.corese.core.storage.api.dataManager.support.model.MutationResult; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.DisplayName; + +import java.util.Set; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Integration tests for CoresGraphDataManager. + * + */ +@DisplayName("CoreseGraphDataManager Integration Tests") +class CoreseGraphDataManagerIntegrationTest { + + private CoreseGraphDataManager dataManager; + + @BeforeEach + void setUp() throws DataManagerException { + dataManager = new CoreseGraphDataManagerBuilder().build(); + + // Initialize with transaction support enabled + DataManagerConfig config = DataManagerConfig.builder() + .debug(false) + .transactionSupport(true) + .build(); + dataManager.getLifecycle().initialize(config); + } + + @Test + @DisplayName("Bulk insert of 100 edges should succeed") + void testBulkOperations() throws DataManagerException { + java.util.List edges = new java.util.ArrayList<>(); + + for (int i = 0; i < 100; i++) { + Node s = dataManager.getGraph().addResource("http://example.org/s" + i); + Node p = dataManager.getGraph().addProperty("http://example.org/p"); + Node o = dataManager.getGraph().addResource("http://example.org/o" + i); + edges.add(dataManager.getGraph().create(dataManager.getGraph().getDefaultGraphNode(), s, p, o)); + } + + MutationResult result = dataManager.getBulkOperations().insertBatch(edges); + + assertTrue(result.isCompleteSuccess()); + assertEquals(100, result.getSuccessCount()); + assertEquals(100, dataManager.getGraph().size()); + } + + @Test + @DisplayName("Metadata operations should return correct statistics") + void testMetadataOperations() throws DataManagerException { + // Add test data + Node s = dataManager.getGraph().addResource("http://example.org/subject"); + Node p1 = dataManager.getGraph().addProperty("http://example.org/p1"); + Node p2 = dataManager.getGraph().addProperty("http://example.org/p2"); + Node o = dataManager.getGraph().addResource("http://example.org/object"); + + Edge edge1 = dataManager.getGraph().create(dataManager.getGraph().getDefaultGraphNode(), s, p1, o); + Edge edge2 = dataManager.getGraph().create(dataManager.getGraph().getDefaultGraphNode(), s, p2, o); + + dataManager.getMutationOperations().insertEdge(edge1); + dataManager.getMutationOperations().insertEdge(edge2); + + // Get metadata + Set predicates = dataManager.getMetadataOperations().getAllPredicates(); + assertTrue(predicates.size() >= 2); + + GraphStatistics stats = dataManager.getMetadataOperations().getStatistics(); + assertEquals(2, stats.getEdgeCount()); + assertTrue(stats.getNodeCount() >= 2); + assertTrue(stats.getPredicateCount() >= 2); + } + + @Test + @DisplayName("Query with predicate pattern should find matching edges") + void testQueryWithPattern() throws DataManagerException { + // Add test data + Node s = dataManager.getGraph().addResource("http://example.org/Alice"); + Node p = dataManager.getGraph().addProperty("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"); + Node o = dataManager.getGraph().addResource("http://example.org/Person"); + Edge edge = dataManager.getGraph().create(dataManager.getGraph().getDefaultGraphNode(), s, p, o); + + dataManager.getMutationOperations().insertEdge(edge); + + // Query with pattern + EdgePattern pattern = EdgePattern.builder() + .predicate(p) + .build(); + + try (Stream edges = dataManager.getQueryOperations().query(pattern)) { + long count = edges.count(); + assertEquals(1, count); + } + } + + @Test + @DisplayName("Clear all data should empty the graph") + void testClearAllData() throws DataManagerException { + // Add data + for (int i = 0; i < 10; i++) { + Node s = dataManager.getGraph().addResource("http://example.org/s" + i); + Node p = dataManager.getGraph().addProperty("http://example.org/p"); + Node o = dataManager.getGraph().addResource("http://example.org/o" + i); + dataManager.getMutationOperations().insertEdge(s, p, o, null); + } + + assertTrue(dataManager.getGraph().size() > 0); + + // Clear all + MutationResult result = dataManager.getBulkOperations().clearAll(); + // For bulk operations, check if there were deletions + assertTrue(result.getTotalAttempted() > 0 || dataManager.getGraph().size() == 0); + + assertEquals(0, dataManager.getGraph().size()); + } + + @Test + @DisplayName("Lifecycle shutdown should properly clean up resources") + void testLifecycleShutdown() throws DataManagerException { + assertTrue(dataManager.getLifecycle().isInitialized()); + + dataManager.getLifecycle().shutdown(); + + assertFalse(dataManager.getLifecycle().isInitialized()); + } +} \ No newline at end of file From c94a7e5e414caa90aef6a02a82e95e26c36be6e1 Mon Sep 17 00:00:00 2001 From: "AD\\aabdoun" Date: Wed, 14 Jan 2026 10:17:59 +0100 Subject: [PATCH 6/8] test unitaire --- .../MetadataOperationsImplTest.java | 316 ++++++++++++++++++ .../operations/QueryOperationsImplTest.java | 291 ++++++++++++++++ 2 files changed, 607 insertions(+) create mode 100644 src/test/java/fr/inria/corese/core/storage/impl/operations/MetadataOperationsImplTest.java create mode 100644 src/test/java/fr/inria/corese/core/storage/impl/operations/QueryOperationsImplTest.java diff --git a/src/test/java/fr/inria/corese/core/storage/impl/operations/MetadataOperationsImplTest.java b/src/test/java/fr/inria/corese/core/storage/impl/operations/MetadataOperationsImplTest.java new file mode 100644 index 000000000..2e440c40a --- /dev/null +++ b/src/test/java/fr/inria/corese/core/storage/impl/operations/MetadataOperationsImplTest.java @@ -0,0 +1,316 @@ +package fr.inria.corese.core.storage.impl.operations; + +import fr.inria.corese.core.Graph; +import fr.inria.corese.core.kgram.api.core.Node; +import fr.inria.corese.core.storage.api.dataManager.support.exception.DataManagerException; +import fr.inria.corese.core.storage.api.dataManager.support.model.GraphStatistics; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Unit tests for MetadataOperationsImpl. + */ +@DisplayName("MetadataOperationsImpl Tests") +class MetadataOperationsImplTest { + + private MetadataOperationsImpl metadataOperations; + private Graph graph; + + @BeforeEach + void setUp() { + graph = new Graph(); + metadataOperations = new MetadataOperationsImpl(graph); + } + + @Test + @DisplayName("Constructor with null graph should throw IllegalArgumentException") + void testConstructorWithNullGraphThrows() { + assertThrows(IllegalArgumentException.class, () -> new MetadataOperationsImpl(null)); + } + + @Test + @DisplayName("Get all predicates on empty graph should return empty set") + void testGetAllPredicatesOnEmptyGraph() throws DataManagerException { + Set predicates = metadataOperations.getAllPredicates(); + + assertNotNull(predicates); + assertTrue(predicates.isEmpty()); + } + + + @Test + @DisplayName("Get all predicates should return all unique predicates") + void testGetAllPredicates() throws DataManagerException { + // Add test data with different predicates + Node s = graph.addResource("http://example.org/subject"); + Node p1 = graph.addProperty("http://example.org/predicate1"); + Node p2 = graph.addProperty("http://example.org/predicate2"); + Node o = graph.addResource("http://example.org/object"); + + graph.addEdge(s, p1, o); + graph.addEdge(s, p2, o); + + Set predicates = metadataOperations.getAllPredicates(); + + assertNotNull(predicates); + assertTrue(predicates.size() >= 2); + assertTrue(predicates.contains(p1)); + assertTrue(predicates.contains(p2)); + } + + @Test + @DisplayName("Get predicates for specific context") + void testGetPredicatesForContext() throws DataManagerException { + Node context = graph.addGraph("http://example.org/graph1"); + Node s = graph.addResource("http://example.org/subject"); + Node p = graph.addProperty("http://example.org/predicate"); + Node o = graph.addResource("http://example.org/object"); + + graph.addEdge(context, s, p, o); + + Set predicates = metadataOperations.getPredicates(context); + + assertNotNull(predicates); + assertFalse(predicates.isEmpty()); + assertTrue(predicates.contains(p)); + } + + @Test + @DisplayName("Get all nodes on empty graph should return empty set") + void testGetAllNodesOnEmptyGraph() throws DataManagerException { + Set nodes = metadataOperations.getAllNodes(); + + assertNotNull(nodes); + assertTrue(nodes.isEmpty()); + } + + @Test + @DisplayName("Get all nodes should return all subjects and objects") + void testGetAllNodes() throws DataManagerException { + Node s1 = graph.addResource("http://example.org/subject1"); + Node s2 = graph.addResource("http://example.org/subject2"); + Node p = graph.addProperty("http://example.org/predicate"); + Node o1 = graph.addResource("http://example.org/object1"); + Node o2 = graph.addResource("http://example.org/object2"); + + graph.addEdge(s1, p, o1); + graph.addEdge(s2, p, o2); + + Set nodes = metadataOperations.getAllNodes(); + + assertNotNull(nodes); + assertTrue(nodes.size() >= 4); + assertTrue(nodes.contains(s1)); + assertTrue(nodes.contains(s2)); + assertTrue(nodes.contains(o1)); + assertTrue(nodes.contains(o2)); + } + + @Test + @DisplayName("Get nodes for specific context") + void testGetNodesForContext() throws DataManagerException { + Node context = graph.addGraph("http://example.org/graph1"); + Node s = graph.addResource("http://example.org/subject"); + Node p = graph.addProperty("http://example.org/predicate"); + Node o = graph.addResource("http://example.org/object"); + + graph.addEdge(context, s, p, o); + + Set nodes = metadataOperations.getNodes(context); + + assertNotNull(nodes); + assertTrue(nodes.size() >= 2); + assertTrue(nodes.contains(s)); + assertTrue(nodes.contains(o)); + } + + @Test + @DisplayName("Get contexts on empty graph should return empty set") + void testGetContextsOnEmptyGraph() throws DataManagerException { + Set contexts = metadataOperations.getContexts(); + + assertNotNull(contexts); + // May contain default graph + assertTrue(contexts.isEmpty() || contexts.size() == 1); + } + + @Test + @DisplayName("Get contexts should return all named graphs") + void testGetContexts() throws DataManagerException { + Node context1 = graph.addGraph("http://example.org/graph1"); + Node context2 = graph.addGraph("http://example.org/graph2"); + Node s = graph.addResource("http://example.org/subject"); + Node p = graph.addProperty("http://example.org/predicate"); + Node o = graph.addResource("http://example.org/object"); + + graph.addEdge(context1, s, p, o); + graph.addEdge(context2, s, p, o); + + Set contexts = metadataOperations.getContexts(); + + assertNotNull(contexts); + assertTrue(contexts.size() >= 2); + assertTrue(contexts.contains(context1)); + assertTrue(contexts.contains(context2)); + } + + @Test + @DisplayName("Get statistics on empty graph should return zero counts") + void testGetStatisticsOnEmptyGraph() throws DataManagerException { + GraphStatistics stats = metadataOperations.getStatistics(); + + assertNotNull(stats); + assertEquals(0, stats.getEdgeCount()); + assertEquals(0, stats.getNodeCount()); + assertEquals(0, stats.getPredicateCount()); + assertTrue(stats.isEmpty()); + } + + @Test + @DisplayName("Get statistics should return correct counts") + void testGetStatistics() throws DataManagerException { + // Add test data + Node s1 = graph.addResource("http://example.org/s1"); + Node s2 = graph.addResource("http://example.org/s2"); + Node p1 = graph.addProperty("http://example.org/p1"); + Node p2 = graph.addProperty("http://example.org/p2"); + Node o1 = graph.addResource("http://example.org/o1"); + Node o2 = graph.addResource("http://example.org/o2"); + + graph.addEdge(s1, p1, o1); + graph.addEdge(s1, p2, o2); + graph.addEdge(s2, p1, o1); + + GraphStatistics stats = metadataOperations.getStatistics(); + + assertNotNull(stats); + assertEquals(3, stats.getEdgeCount()); + assertTrue(stats.getNodeCount() >= 4); + assertTrue(stats.getPredicateCount() >= 2); + assertFalse(stats.isEmpty()); + assertTrue(stats.hasData()); + } + + @Test + @DisplayName("Get statistics should calculate density") + void testGetStatisticsWithDensity() throws DataManagerException { + // Add some edges + for (int i = 0; i < 5; i++) { + Node s = graph.addResource("http://example.org/s" + i); + Node p = graph.addProperty("http://example.org/p"); + Node o = graph.addResource("http://example.org/o" + i); + graph.addEdge(s, p, o); + } + + GraphStatistics stats = metadataOperations.getStatistics(); + + assertNotNull(stats); + assertTrue(stats.getEdgeCount() > 0); + assertTrue(stats.getDensity() >= 0.0); + assertTrue(stats.getDensity() <= 1.0); + } + + @Test + @DisplayName("Get statistics should calculate average degree") + void testGetStatisticsWithAverageDegree() throws DataManagerException { + Node s = graph.addResource("http://example.org/subject"); + Node p = graph.addProperty("http://example.org/predicate"); + + for (int i = 0; i < 3; i++) { + Node o = graph.addResource("http://example.org/o" + i); + graph.addEdge(s, p, o); + } + + GraphStatistics stats = metadataOperations.getStatistics(); + + assertNotNull(stats); + assertTrue(stats.getAverageDegree() > 0.0); + } + + @Test + @DisplayName("Get predicates with null context should return all predicates") + void testGetPredicatesWithNullContext() throws DataManagerException { + Node s = graph.addResource("http://example.org/subject"); + Node p = graph.addProperty("http://example.org/predicate"); + Node o = graph.addResource("http://example.org/object"); + + graph.addEdge(s, p, o); + + Set predicates = metadataOperations.getPredicates(null); + + assertNotNull(predicates); + assertTrue(predicates.contains(p)); + } + + @Test + @DisplayName("Get nodes with null context should return all nodes") + void testGetNodesWithNullContext() throws DataManagerException { + Node s = graph.addResource("http://example.org/subject"); + Node p = graph.addProperty("http://example.org/predicate"); + Node o = graph.addResource("http://example.org/object"); + + graph.addEdge(s, p, o); + + Set nodes = metadataOperations.getNodes(null); + + assertNotNull(nodes); + assertTrue(nodes.contains(s)); + assertTrue(nodes.contains(o)); + } + + @Test + @DisplayName("Multiple contexts should be tracked separately") + void testMultipleContexts() throws DataManagerException { + Node ctx1 = graph.addGraph("http://example.org/graph1"); + Node ctx2 = graph.addGraph("http://example.org/graph2"); + Node s = graph.addResource("http://example.org/subject"); + Node p = graph.addProperty("http://example.org/predicate"); + Node o = graph.addResource("http://example.org/object"); + + graph.addEdge(ctx1, s, p, o); + graph.addEdge(ctx2, s, p, o); + + Set contexts = metadataOperations.getContexts(); + + assertNotNull(contexts); + assertTrue(contexts.contains(ctx1)); + assertTrue(contexts.contains(ctx2)); + } + + @Test + @DisplayName("Statistics should track context count") + void testStatisticsContextCount() throws DataManagerException { + Node ctx1 = graph.addGraph("http://example.org/graph1"); + Node ctx2 = graph.addGraph("http://example.org/graph2"); + Node s = graph.addResource("http://example.org/subject"); + Node p = graph.addProperty("http://example.org/predicate"); + Node o = graph.addResource("http://example.org/object"); + + graph.addEdge(ctx1, s, p, o); + graph.addEdge(ctx2, s, p, o); + + GraphStatistics stats = metadataOperations.getStatistics(); + + assertNotNull(stats); + assertTrue(stats.getContextCount() >= 2); + } + + @Test + @DisplayName("Returned sets should be unmodifiable") + void testReturnedSetsAreUnmodifiable() throws DataManagerException { + Node s = graph.addResource("http://example.org/subject"); + Node p = graph.addProperty("http://example.org/predicate"); + Node o = graph.addResource("http://example.org/object"); + + graph.addEdge(s, p, o); + + Set predicates = metadataOperations.getAllPredicates(); + + assertThrows(UnsupportedOperationException.class, () -> predicates.add(graph.addProperty("http://example.org/another"))); + } +} \ No newline at end of file diff --git a/src/test/java/fr/inria/corese/core/storage/impl/operations/QueryOperationsImplTest.java b/src/test/java/fr/inria/corese/core/storage/impl/operations/QueryOperationsImplTest.java new file mode 100644 index 000000000..65c210515 --- /dev/null +++ b/src/test/java/fr/inria/corese/core/storage/impl/operations/QueryOperationsImplTest.java @@ -0,0 +1,291 @@ +package fr.inria.corese.core.storage.impl.operations; + +import fr.inria.corese.core.Graph; +import fr.inria.corese.core.kgram.api.core.Edge; +import fr.inria.corese.core.kgram.api.core.Node; +import fr.inria.corese.core.storage.api.dataManager.support.exception.DataManagerException; +import fr.inria.corese.core.storage.api.dataManager.support.model.EdgePattern; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Unit tests for QueryOperationsImpl. + * + */ +@DisplayName("QueryOperationsImpl Tests") +class QueryOperationsImplTest { + + private QueryOperationsImpl queryOperations; + private Graph graph; + + @BeforeEach + void setUp() { + graph = new Graph(); + queryOperations = new QueryOperationsImpl(graph); + } + + @Test + @DisplayName("Constructor with null graph should throw IllegalArgumentException") + void testConstructorWithNullGraphThrows() { + assertThrows(IllegalArgumentException.class, () -> new QueryOperationsImpl(null)); + } + + @Test + @DisplayName("Query with null pattern should throw IllegalArgumentException") + void testQueryWithNullPatternThrows() { + assertThrows(IllegalArgumentException.class, () -> queryOperations.query(null)); + } + + @Test + @DisplayName("Query with match-all pattern on empty graph should return empty stream") + void testQueryMatchAllOnEmptyGraph() throws DataManagerException { + EdgePattern pattern = EdgePattern.builder().build(); + + try (Stream edges = queryOperations.query(pattern)) { + assertEquals(0, edges.count()); + } + } + + @Test + @DisplayName("Query with predicate pattern should return matching edges") + void testQueryWithPredicatePattern() throws DataManagerException { + // Add test data + Node s = graph.addResource("http://example.org/subject"); + Node p = graph.addProperty("http://example.org/predicate"); + Node o = graph.addResource("http://example.org/object"); + graph.addEdge(s, p, o); + + EdgePattern pattern = EdgePattern.builder() + .predicate(p) + .build(); + + try (Stream edges = queryOperations.query(pattern)) { + assertEquals(1, edges.count()); + } + } + + @Test + @DisplayName("Query with subject and predicate pattern should match specific edges") + void testQueryWithSubjectAndPredicatePattern() throws DataManagerException { + Node s1 = graph.addResource("http://example.org/s1"); + Node s2 = graph.addResource("http://example.org/s2"); + Node p = graph.addProperty("http://example.org/predicate"); + Node o = graph.addResource("http://example.org/object"); + + graph.addEdge(s1, p, o); + graph.addEdge(s2, p, o); + + EdgePattern pattern = EdgePattern.builder() + .subject(s1) + .predicate(p) + .build(); + + try (Stream edges = queryOperations.query(pattern)) { + long count = edges.count(); + assertEquals(1, count); + } + } + + + @Test + @DisplayName("Count with match-all pattern should return total edge count") + void testCountWithMatchAllPattern() throws DataManagerException { + // Add 5 edges + for (int i = 0; i < 5; i++) { + Node s = graph.addResource("http://example.org/s" + i); + Node p = graph.addProperty("http://example.org/p"); + Node o = graph.addResource("http://example.org/o" + i); + graph.addEdge(s, p, o); + } + + EdgePattern pattern = EdgePattern.builder().build(); + + long count = queryOperations.count(pattern); + + assertEquals(5, count); + } + + @Test + @DisplayName("Count on empty graph should return 0") + void testCountOnEmptyGraph() throws DataManagerException { + EdgePattern pattern = EdgePattern.builder().build(); + + long count = queryOperations.count(pattern); + + assertEquals(0, count); + } + + @Test + @DisplayName("Count with specific predicate should return matching count") + void testCountWithPredicatePattern() throws DataManagerException { + Node p1 = graph.addProperty("http://example.org/p1"); + Node p2 = graph.addProperty("http://example.org/p2"); + Node s = graph.addResource("http://example.org/subject"); + Node o = graph.addResource("http://example.org/object"); + + graph.addEdge(s, p1, o); + graph.addEdge(s, p1, o); + graph.addEdge(s, p2, o); + + EdgePattern pattern = EdgePattern.builder() + .predicate(p1) + .build(); + + long count = queryOperations.count(pattern); + + assertTrue(count >= 1); + } + + @Test + @DisplayName("Exists should return true when pattern matches") + void testExistsReturnsTrueWhenMatches() throws DataManagerException { + Node s = graph.addResource("http://example.org/subject"); + Node p = graph.addProperty("http://example.org/predicate"); + Node o = graph.addResource("http://example.org/object"); + graph.addEdge(s, p, o); + + EdgePattern pattern = EdgePattern.builder() + .predicate(p) + .build(); + + boolean exists = queryOperations.exists(pattern); + + assertTrue(exists); + } + + @Test + @DisplayName("Exists should return false when pattern does not match") + void testExistsReturnsFalseWhenNoMatches() throws DataManagerException { + Node p = graph.addProperty("http://example.org/nonexistent"); + + EdgePattern pattern = EdgePattern.builder() + .predicate(p) + .build(); + + boolean exists = queryOperations.exists(pattern); + + assertFalse(exists); + } + + + @Test + @DisplayName("Find with null edge should throw IllegalArgumentException") + void testFindWithNullEdgeThrows() { + assertThrows(IllegalArgumentException.class, () -> queryOperations.find(null)); + } + + @Test + @DisplayName("Query with context filter should return edges from that context") + void testQueryWithContextFilter() throws DataManagerException { + Node context = graph.addGraph("http://example.org/graph1"); + Node s = graph.addResource("http://example.org/subject"); + Node p = graph.addProperty("http://example.org/predicate"); + Node o = graph.addResource("http://example.org/object"); + + graph.addEdge(context, s, p, o); + + EdgePattern pattern = EdgePattern.builder() + .contexts(java.util.List.of(context)) + .build(); + + try (Stream edges = queryOperations.query(pattern)) { + assertTrue(edges.findAny().isPresent()); + } + } + + @Test + @DisplayName("Query should support multiple predicates") + void testQueryWithMultiplePredicates() throws DataManagerException { + Node s = graph.addResource("http://example.org/subject"); + Node p1 = graph.addProperty("http://example.org/p1"); + Node p2 = graph.addProperty("http://example.org/p2"); + Node p3 = graph.addProperty("http://example.org/p3"); + Node o = graph.addResource("http://example.org/object"); + + graph.addEdge(s, p1, o); + graph.addEdge(s, p2, o); + graph.addEdge(s, p3, o); + + EdgePattern pattern = EdgePattern.builder() + .subject(s) + .build(); + + try (Stream edges = queryOperations.query(pattern)) { + assertEquals(3, edges.count()); + } + } + + + @Test + @DisplayName("Count should use optimization when available") + void testCountOptimization() throws DataManagerException { + // Add many edges + for (int i = 0; i < 100; i++) { + Node s = graph.addResource("http://example.org/s" + i); + Node p = graph.addProperty("http://example.org/p"); + Node o = graph.addResource("http://example.org/o" + i); + graph.addEdge(s, p, o); + } + + EdgePattern pattern = EdgePattern.builder().build(); + + long count = queryOperations.count(pattern); + + assertEquals(100, count); + } + + @Test + @DisplayName("Query with object pattern should match edges") + void testQueryWithObjectPattern() throws DataManagerException { + Node s = graph.addResource("http://example.org/subject"); + Node p = graph.addProperty("http://example.org/predicate"); + Node o1 = graph.addResource("http://example.org/object1"); + Node o2 = graph.addResource("http://example.org/object2"); + + graph.addEdge(s, p, o1); + graph.addEdge(s, p, o2); + + EdgePattern pattern = EdgePattern.builder() + .object(o1) + .build(); + + try (Stream edges = queryOperations.query(pattern)) { + long count = edges.count(); + assertTrue(count >= 1); + } + } + + @Test + @DisplayName("Exists on empty graph should return false") + void testExistsOnEmptyGraph() throws DataManagerException { + EdgePattern pattern = EdgePattern.builder().build(); + + boolean exists = queryOperations.exists(pattern); + + assertFalse(exists); + } + + @Test + @DisplayName("Multiple queries should return consistent results") + void testMultipleQueriesConsistency() throws DataManagerException { + Node s = graph.addResource("http://example.org/subject"); + Node p = graph.addProperty("http://example.org/predicate"); + Node o = graph.addResource("http://example.org/object"); + graph.addEdge(s, p, o); + + EdgePattern pattern = EdgePattern.builder() + .predicate(p) + .build(); + + for (int i = 0; i < 3; i++) { + try (Stream edges = queryOperations.query(pattern)) { + assertEquals(1, edges.count()); + } + } + } +} \ No newline at end of file From 3440c7a3a47824072ddd47e26904caf8196747a2 Mon Sep 17 00:00:00 2001 From: "AD\\aabdoun" Date: Mon, 26 Jan 2026 16:49:24 +0100 Subject: [PATCH 7/8] Refactor DataManager architecture revue --- .../fr/inria/corese/core/api/DataBroker.java | 2 +- .../core/load/AddTripleHelperDataManager.java | 2 +- .../java/fr/inria/corese/core/load/Load.java | 2 +- .../corese/core/load/TripleCreatorBase.java | 2 +- .../load/jsonld/CoreseJsonTripleCallback.java | 2 +- .../core/load/rdfa/CoreseRDFaTripleSink.java | 2 +- .../core/logic/BrokerDistanceDataManager.java | 2 +- .../corese/core/logic/ClosureDataManager.java | 2 +- .../fr/inria/corese/core/logic/Distance.java | 2 +- .../producer/DataBrokerConstructExtern.java | 2 +- .../core/producer/DataBrokerExtern.java | 2 +- .../corese/core/producer/DataBrokerLocal.java | 2 +- .../corese/core/producer/MetadataManager.java | 2 +- .../corese/core/query/DatasetManager.java | 2 +- .../inria/corese/core/query/PluginImpl.java | 3 +- .../inria/corese/core/query/ProducerImpl.java | 2 +- .../corese/core/query/ProviderService.java | 2 +- .../inria/corese/core/query/QueryProcess.java | 2 +- .../corese/core/query/StorageFactory.java | 2 +- .../fr/inria/corese/core/rule/Cleaner.java | 2 +- .../fr/inria/corese/core/rule/RuleEngine.java | 2 +- .../fr/inria/corese/core/shacl/Shacl.java | 2 +- .../core/storage/CoreseGraphDataManager.java | 16 ++-- .../CoreseGraphDataManagerBuilder.java | 2 +- .../lifecycle/DataManagerLifecycle.java | 64 ------------- .../DataManager.java | 2 +- .../DataManagerBuilder.java | 2 +- .../DataManagerRead.java | 2 +- .../DataManagerUpdate.java | 2 +- .../lifecycle/DataManagerLifecycle.java | 94 +++++++++++++++++++ .../lifecycle/LifecycleState.java | 4 +- .../operations/BulkOperations.java | 31 +++++- .../operations/MetadataOperations.java | 6 +- .../operations/MutationOperations.java | 6 +- .../operations/QueryOperations.java | 6 +- .../support/config/DataManagerConfig.java | 43 ++++++--- .../exception/DataManagerException.java | 2 +- .../support/exception/ErrorCode.java | 11 ++- .../support/model/EdgePattern.java | 2 +- .../support/model/GraphStatistics.java | 2 +- .../support/model/MutationResult.java | 2 +- .../transaction/IsolationLevel.java | 2 +- .../transaction/Transaction.java | 4 +- .../transaction/TransactionManager.java | 4 +- .../transaction/TransactionState.java | 2 +- .../impl/lifecycle/LifecycleManagerImpl.java | 12 +-- .../impl/operations/BulkOperationsImpl.java | 91 +++++++++++++++++- .../operations/MetadataOperationsImpl.java | 8 +- .../operations/MutationOperationsImpl.java | 8 +- .../impl/operations/QueryOperationsImpl.java | 8 +- .../impl/transaction/TransactionImpl.java | 10 +- .../transaction/TransactionManagerImpl.java | 10 +- .../corese/core/transform/Transformer.java | 2 +- .../fr/inria/corese/core/workflow/Data.java | 2 +- ...CoreseGraphDataManagerIntegrationTest.java | 30 +++--- .../lifecycle/LifecycleManagerImplTest.java | 57 ++++++++--- .../operations/BulkOperationsImplTest.java | 4 +- .../MetadataOperationsImplTest.java | 4 +- .../MutationOperationsImplTest.java | 4 +- .../operations/QueryOperationsImplTest.java | 4 +- 60 files changed, 392 insertions(+), 218 deletions(-) delete mode 100644 src/main/java/fr/inria/corese/core/storage/api/dataManager/lifecycle/DataManagerLifecycle.java rename src/main/java/fr/inria/corese/core/storage/api/{dataManager => datamanager}/DataManager.java (98%) rename src/main/java/fr/inria/corese/core/storage/api/{dataManager => datamanager}/DataManagerBuilder.java (83%) rename src/main/java/fr/inria/corese/core/storage/api/{dataManager => datamanager}/DataManagerRead.java (98%) rename src/main/java/fr/inria/corese/core/storage/api/{dataManager => datamanager}/DataManagerUpdate.java (99%) create mode 100644 src/main/java/fr/inria/corese/core/storage/api/datamanager/lifecycle/DataManagerLifecycle.java rename src/main/java/fr/inria/corese/core/storage/api/{dataManager => datamanager}/lifecycle/LifecycleState.java (90%) rename src/main/java/fr/inria/corese/core/storage/api/{dataManager => datamanager}/operations/BulkOperations.java (77%) rename src/main/java/fr/inria/corese/core/storage/api/{dataManager => datamanager}/operations/MetadataOperations.java (91%) rename src/main/java/fr/inria/corese/core/storage/api/{dataManager => datamanager}/operations/MutationOperations.java (94%) rename src/main/java/fr/inria/corese/core/storage/api/{dataManager => datamanager}/operations/QueryOperations.java (91%) rename src/main/java/fr/inria/corese/core/storage/api/{dataManager => datamanager}/support/config/DataManagerConfig.java (71%) rename src/main/java/fr/inria/corese/core/storage/api/{dataManager => datamanager}/support/exception/DataManagerException.java (94%) rename src/main/java/fr/inria/corese/core/storage/api/{dataManager => datamanager}/support/exception/ErrorCode.java (71%) rename src/main/java/fr/inria/corese/core/storage/api/{dataManager => datamanager}/support/model/EdgePattern.java (98%) rename src/main/java/fr/inria/corese/core/storage/api/{dataManager => datamanager}/support/model/GraphStatistics.java (98%) rename src/main/java/fr/inria/corese/core/storage/api/{dataManager => datamanager}/support/model/MutationResult.java (99%) rename src/main/java/fr/inria/corese/core/storage/api/{dataManager => datamanager}/transaction/IsolationLevel.java (92%) rename src/main/java/fr/inria/corese/core/storage/api/{dataManager => datamanager}/transaction/Transaction.java (92%) rename src/main/java/fr/inria/corese/core/storage/api/{dataManager => datamanager}/transaction/TransactionManager.java (94%) rename src/main/java/fr/inria/corese/core/storage/api/{dataManager => datamanager}/transaction/TransactionState.java (91%) diff --git a/src/main/java/fr/inria/corese/core/api/DataBroker.java b/src/main/java/fr/inria/corese/core/api/DataBroker.java index dc9f53f30..8a7e3fbe9 100644 --- a/src/main/java/fr/inria/corese/core/api/DataBroker.java +++ b/src/main/java/fr/inria/corese/core/api/DataBroker.java @@ -7,7 +7,7 @@ import fr.inria.corese.core.kgram.core.Query; import fr.inria.corese.core.logic.RDF; import fr.inria.corese.core.sparql.datatype.DatatypeMap; -import fr.inria.corese.core.storage.api.dataManager.DataManager; +import fr.inria.corese.core.storage.api.datamanager.DataManager; import java.util.List; diff --git a/src/main/java/fr/inria/corese/core/load/AddTripleHelperDataManager.java b/src/main/java/fr/inria/corese/core/load/AddTripleHelperDataManager.java index b8a446624..04f535d2a 100644 --- a/src/main/java/fr/inria/corese/core/load/AddTripleHelperDataManager.java +++ b/src/main/java/fr/inria/corese/core/load/AddTripleHelperDataManager.java @@ -2,7 +2,7 @@ import fr.inria.corese.core.Graph; import fr.inria.corese.core.logic.Entailment; -import fr.inria.corese.core.storage.api.dataManager.DataManager; +import fr.inria.corese.core.storage.api.datamanager.DataManager; import fr.inria.corese.core.kgram.api.core.Edge; import fr.inria.corese.core.kgram.api.core.Node; import fr.inria.corese.core.sparql.datatype.DatatypeMap; diff --git a/src/main/java/fr/inria/corese/core/load/Load.java b/src/main/java/fr/inria/corese/core/load/Load.java index 86f4ffcd0..2ce517738 100644 --- a/src/main/java/fr/inria/corese/core/load/Load.java +++ b/src/main/java/fr/inria/corese/core/load/Load.java @@ -52,7 +52,7 @@ import fr.inria.corese.core.sparql.triple.parser.Constant; import fr.inria.corese.core.sparql.triple.parser.LoadTurtle; import fr.inria.corese.core.sparql.triple.parser.NSManager; -import fr.inria.corese.core.storage.api.dataManager.DataManager; +import fr.inria.corese.core.storage.api.datamanager.DataManager; import fr.inria.corese.core.util.HTTPHeaders; import fr.inria.corese.core.workflow.SemanticWorkflow; import fr.inria.corese.core.workflow.WorkflowParser; diff --git a/src/main/java/fr/inria/corese/core/load/TripleCreatorBase.java b/src/main/java/fr/inria/corese/core/load/TripleCreatorBase.java index 2896151bc..fc8464214 100644 --- a/src/main/java/fr/inria/corese/core/load/TripleCreatorBase.java +++ b/src/main/java/fr/inria/corese/core/load/TripleCreatorBase.java @@ -17,7 +17,7 @@ import fr.inria.corese.core.sparql.triple.parser.AccessRight; import fr.inria.corese.core.sparql.triple.parser.Atom; import fr.inria.corese.core.sparql.triple.parser.Constant; -import fr.inria.corese.core.storage.api.dataManager.DataManager; +import fr.inria.corese.core.storage.api.datamanager.DataManager; /** * Base class for creating and inserting RDF triples into a Graph. diff --git a/src/main/java/fr/inria/corese/core/load/jsonld/CoreseJsonTripleCallback.java b/src/main/java/fr/inria/corese/core/load/jsonld/CoreseJsonTripleCallback.java index 409eee088..05ef9fd70 100644 --- a/src/main/java/fr/inria/corese/core/load/jsonld/CoreseJsonTripleCallback.java +++ b/src/main/java/fr/inria/corese/core/load/jsonld/CoreseJsonTripleCallback.java @@ -11,7 +11,7 @@ import fr.inria.corese.core.load.AddTripleHelperDataManager; import fr.inria.corese.core.load.ILoadSerialization; import fr.inria.corese.core.load.Load; -import fr.inria.corese.core.storage.api.dataManager.DataManager; +import fr.inria.corese.core.storage.api.datamanager.DataManager; /** * Implementation of interface from Jsonld-java (json-ld parser) for adding diff --git a/src/main/java/fr/inria/corese/core/load/rdfa/CoreseRDFaTripleSink.java b/src/main/java/fr/inria/corese/core/load/rdfa/CoreseRDFaTripleSink.java index c36a290f3..9777c5df4 100644 --- a/src/main/java/fr/inria/corese/core/load/rdfa/CoreseRDFaTripleSink.java +++ b/src/main/java/fr/inria/corese/core/load/rdfa/CoreseRDFaTripleSink.java @@ -9,7 +9,7 @@ import fr.inria.corese.core.load.AddTripleHelperDataManager; import fr.inria.corese.core.load.ILoadSerialization; import fr.inria.corese.core.load.Load; -import fr.inria.corese.core.storage.api.dataManager.DataManager; +import fr.inria.corese.core.storage.api.datamanager.DataManager; /** * Implements the interface TripleSink (from semargl) in order to add the diff --git a/src/main/java/fr/inria/corese/core/logic/BrokerDistanceDataManager.java b/src/main/java/fr/inria/corese/core/logic/BrokerDistanceDataManager.java index d63bb258d..aa01bdab6 100644 --- a/src/main/java/fr/inria/corese/core/logic/BrokerDistanceDataManager.java +++ b/src/main/java/fr/inria/corese/core/logic/BrokerDistanceDataManager.java @@ -5,7 +5,7 @@ import fr.inria.corese.core.Graph; import fr.inria.corese.core.query.QueryProcess; -import fr.inria.corese.core.storage.api.dataManager.DataManager; +import fr.inria.corese.core.storage.api.datamanager.DataManager; import fr.inria.corese.core.kgram.api.core.Edge; import fr.inria.corese.core.kgram.api.core.Node; import fr.inria.corese.core.kgram.core.Mappings; diff --git a/src/main/java/fr/inria/corese/core/logic/ClosureDataManager.java b/src/main/java/fr/inria/corese/core/logic/ClosureDataManager.java index 340cb1b7a..524f270b4 100644 --- a/src/main/java/fr/inria/corese/core/logic/ClosureDataManager.java +++ b/src/main/java/fr/inria/corese/core/logic/ClosureDataManager.java @@ -1,7 +1,7 @@ package fr.inria.corese.core.logic; import fr.inria.corese.core.Graph; -import fr.inria.corese.core.storage.api.dataManager.DataManager; +import fr.inria.corese.core.storage.api.datamanager.DataManager; import fr.inria.corese.core.kgram.api.core.Edge; import fr.inria.corese.core.kgram.api.core.ExprType; import fr.inria.corese.core.kgram.api.core.Node; diff --git a/src/main/java/fr/inria/corese/core/logic/Distance.java b/src/main/java/fr/inria/corese/core/logic/Distance.java index 41152c064..d640bff2a 100644 --- a/src/main/java/fr/inria/corese/core/logic/Distance.java +++ b/src/main/java/fr/inria/corese/core/logic/Distance.java @@ -8,7 +8,7 @@ import org.slf4j.LoggerFactory; import fr.inria.corese.core.Graph; -import fr.inria.corese.core.storage.api.dataManager.DataManager; +import fr.inria.corese.core.storage.api.datamanager.DataManager; import fr.inria.corese.core.kgram.api.core.Node; /** diff --git a/src/main/java/fr/inria/corese/core/producer/DataBrokerConstructExtern.java b/src/main/java/fr/inria/corese/core/producer/DataBrokerConstructExtern.java index f453e67d7..a16a41e9b 100644 --- a/src/main/java/fr/inria/corese/core/producer/DataBrokerConstructExtern.java +++ b/src/main/java/fr/inria/corese/core/producer/DataBrokerConstructExtern.java @@ -7,7 +7,7 @@ import fr.inria.corese.core.api.DataBrokerConstruct; import fr.inria.corese.core.load.Load; import fr.inria.corese.core.load.LoadException; -import fr.inria.corese.core.storage.api.dataManager.DataManager; +import fr.inria.corese.core.storage.api.datamanager.DataManager; import fr.inria.corese.core.kgram.api.core.Edge; import fr.inria.corese.core.kgram.api.core.Node; import fr.inria.corese.core.kgram.core.Query; diff --git a/src/main/java/fr/inria/corese/core/producer/DataBrokerExtern.java b/src/main/java/fr/inria/corese/core/producer/DataBrokerExtern.java index 100023eef..a60ee0aea 100644 --- a/src/main/java/fr/inria/corese/core/producer/DataBrokerExtern.java +++ b/src/main/java/fr/inria/corese/core/producer/DataBrokerExtern.java @@ -1,7 +1,7 @@ package fr.inria.corese.core.producer; import fr.inria.corese.core.api.DataBroker; -import fr.inria.corese.core.storage.api.dataManager.DataManager; +import fr.inria.corese.core.storage.api.datamanager.DataManager; /** * Broker between ProducerImpl and external DataManager diff --git a/src/main/java/fr/inria/corese/core/producer/DataBrokerLocal.java b/src/main/java/fr/inria/corese/core/producer/DataBrokerLocal.java index 2544aee89..ed15ee7bd 100644 --- a/src/main/java/fr/inria/corese/core/producer/DataBrokerLocal.java +++ b/src/main/java/fr/inria/corese/core/producer/DataBrokerLocal.java @@ -4,7 +4,7 @@ import fr.inria.corese.core.Graph; import fr.inria.corese.core.api.DataBroker; -import fr.inria.corese.core.storage.api.dataManager.DataManager; +import fr.inria.corese.core.storage.api.datamanager.DataManager; import fr.inria.corese.core.kgram.api.core.Edge; import fr.inria.corese.core.kgram.api.core.Node; import fr.inria.corese.core.kgram.core.Query; diff --git a/src/main/java/fr/inria/corese/core/producer/MetadataManager.java b/src/main/java/fr/inria/corese/core/producer/MetadataManager.java index d7444650d..c1f8d96c0 100644 --- a/src/main/java/fr/inria/corese/core/producer/MetadataManager.java +++ b/src/main/java/fr/inria/corese/core/producer/MetadataManager.java @@ -3,7 +3,7 @@ import fr.inria.corese.core.kgram.api.core.Edge; import fr.inria.corese.core.kgram.api.core.Node; import fr.inria.corese.core.logic.Distance; -import fr.inria.corese.core.storage.api.dataManager.DataManager; +import fr.inria.corese.core.storage.api.datamanager.DataManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/src/main/java/fr/inria/corese/core/query/DatasetManager.java b/src/main/java/fr/inria/corese/core/query/DatasetManager.java index 03095479c..c6d904512 100644 --- a/src/main/java/fr/inria/corese/core/query/DatasetManager.java +++ b/src/main/java/fr/inria/corese/core/query/DatasetManager.java @@ -5,7 +5,7 @@ import fr.inria.corese.core.rule.RuleEngine; import fr.inria.corese.core.storage.CoreseGraphDataManagerBuilder; import fr.inria.corese.core.storage.DataManagerJava; -import fr.inria.corese.core.storage.api.dataManager.DataManager; +import fr.inria.corese.core.storage.api.datamanager.DataManager; import fr.inria.corese.core.util.Property; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/src/main/java/fr/inria/corese/core/query/PluginImpl.java b/src/main/java/fr/inria/corese/core/query/PluginImpl.java index 555d95503..07a032a3f 100644 --- a/src/main/java/fr/inria/corese/core/query/PluginImpl.java +++ b/src/main/java/fr/inria/corese/core/query/PluginImpl.java @@ -42,9 +42,8 @@ import fr.inria.corese.core.sparql.triple.function.term.Binding; import fr.inria.corese.core.sparql.triple.parser.*; import fr.inria.corese.core.sparql.triple.parser.Access.Level; -import fr.inria.corese.core.storage.api.dataManager.DataManager; +import fr.inria.corese.core.storage.api.datamanager.DataManager; import fr.inria.corese.core.transform.TemplateVisitor; -import fr.inria.corese.core.transform.Transformer; import fr.inria.corese.core.transform.TransformerUtils; import fr.inria.corese.core.util.GraphListen; import fr.inria.corese.core.util.MappingsGraph; diff --git a/src/main/java/fr/inria/corese/core/query/ProducerImpl.java b/src/main/java/fr/inria/corese/core/query/ProducerImpl.java index 0d5fc4db3..a9ea28c3e 100644 --- a/src/main/java/fr/inria/corese/core/query/ProducerImpl.java +++ b/src/main/java/fr/inria/corese/core/query/ProducerImpl.java @@ -19,7 +19,7 @@ import fr.inria.corese.core.sparql.datatype.DatatypeMap; import fr.inria.corese.core.sparql.triple.parser.AccessRight; import fr.inria.corese.core.sparql.triple.parser.Metadata; -import fr.inria.corese.core.storage.api.dataManager.DataManager; +import fr.inria.corese.core.storage.api.datamanager.DataManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/src/main/java/fr/inria/corese/core/query/ProviderService.java b/src/main/java/fr/inria/corese/core/query/ProviderService.java index f9e7b2cce..87dbca379 100644 --- a/src/main/java/fr/inria/corese/core/query/ProviderService.java +++ b/src/main/java/fr/inria/corese/core/query/ProviderService.java @@ -25,7 +25,7 @@ import fr.inria.corese.core.sparql.triple.parser.context.ContextLog; import fr.inria.corese.core.storage.CoreseGraphDataManagerBuilder; import fr.inria.corese.core.storage.DataManagerJava; -import fr.inria.corese.core.storage.api.dataManager.DataManager; +import fr.inria.corese.core.storage.api.datamanager.DataManager; import fr.inria.corese.core.util.Property; import jakarta.ws.rs.ProcessingException; import jakarta.ws.rs.client.ResponseProcessingException; diff --git a/src/main/java/fr/inria/corese/core/query/QueryProcess.java b/src/main/java/fr/inria/corese/core/query/QueryProcess.java index 54bc89fb9..7c61e6926 100755 --- a/src/main/java/fr/inria/corese/core/query/QueryProcess.java +++ b/src/main/java/fr/inria/corese/core/query/QueryProcess.java @@ -35,7 +35,7 @@ import fr.inria.corese.core.sparql.triple.parser.*; import fr.inria.corese.core.sparql.triple.parser.Access.Feature; import fr.inria.corese.core.sparql.triple.parser.Access.Level; -import fr.inria.corese.core.storage.api.dataManager.DataManager; +import fr.inria.corese.core.storage.api.datamanager.DataManager; import fr.inria.corese.core.util.Extension; import fr.inria.corese.core.util.Property; import jakarta.ws.rs.client.ResponseProcessingException; diff --git a/src/main/java/fr/inria/corese/core/query/StorageFactory.java b/src/main/java/fr/inria/corese/core/query/StorageFactory.java index 127d755e7..08c6c6103 100644 --- a/src/main/java/fr/inria/corese/core/query/StorageFactory.java +++ b/src/main/java/fr/inria/corese/core/query/StorageFactory.java @@ -2,7 +2,7 @@ import fr.inria.corese.core.query.DatasetManager.TypeDataBase; import fr.inria.corese.core.sparql.triple.parser.URLServer; -import fr.inria.corese.core.storage.api.dataManager.DataManager; +import fr.inria.corese.core.storage.api.datamanager.DataManager; import java.security.InvalidParameterException; import java.util.Collection; diff --git a/src/main/java/fr/inria/corese/core/rule/Cleaner.java b/src/main/java/fr/inria/corese/core/rule/Cleaner.java index 0ad3ad154..ec72711b6 100644 --- a/src/main/java/fr/inria/corese/core/rule/Cleaner.java +++ b/src/main/java/fr/inria/corese/core/rule/Cleaner.java @@ -10,7 +10,7 @@ import fr.inria.corese.core.query.QueryProcess; import fr.inria.corese.core.sparql.exceptions.EngineException; import fr.inria.corese.core.sparql.triple.function.term.Binding; -import fr.inria.corese.core.storage.api.dataManager.DataManager; +import fr.inria.corese.core.storage.api.datamanager.DataManager; import fr.inria.corese.core.util.Property; import java.io.IOException; diff --git a/src/main/java/fr/inria/corese/core/rule/RuleEngine.java b/src/main/java/fr/inria/corese/core/rule/RuleEngine.java index e087ae759..5371611ea 100644 --- a/src/main/java/fr/inria/corese/core/rule/RuleEngine.java +++ b/src/main/java/fr/inria/corese/core/rule/RuleEngine.java @@ -30,7 +30,7 @@ import fr.inria.corese.core.sparql.triple.parser.Access.Feature; import fr.inria.corese.core.sparql.triple.parser.Access.Level; import fr.inria.corese.core.sparql.triple.printer.SPIN; -import fr.inria.corese.core.storage.api.dataManager.DataManager; +import fr.inria.corese.core.storage.api.datamanager.DataManager; import fr.inria.corese.core.util.Property; import fr.inria.corese.core.visitor.solver.QuerySolverVisitorRule; import org.slf4j.Logger; diff --git a/src/main/java/fr/inria/corese/core/shacl/Shacl.java b/src/main/java/fr/inria/corese/core/shacl/Shacl.java index 7942e865c..571a9fe6a 100644 --- a/src/main/java/fr/inria/corese/core/shacl/Shacl.java +++ b/src/main/java/fr/inria/corese/core/shacl/Shacl.java @@ -7,7 +7,7 @@ import fr.inria.corese.core.logic.RDF; import fr.inria.corese.core.producer.DataProducer; import fr.inria.corese.core.query.QueryProcess; -import fr.inria.corese.core.storage.api.dataManager.DataManager; +import fr.inria.corese.core.storage.api.datamanager.DataManager; import fr.inria.corese.core.kgram.api.core.Edge; import fr.inria.corese.core.sparql.api.IDatatype; import fr.inria.corese.core.sparql.datatype.DatatypeMap; diff --git a/src/main/java/fr/inria/corese/core/storage/CoreseGraphDataManager.java b/src/main/java/fr/inria/corese/core/storage/CoreseGraphDataManager.java index 56a1c16b4..551251957 100644 --- a/src/main/java/fr/inria/corese/core/storage/CoreseGraphDataManager.java +++ b/src/main/java/fr/inria/corese/core/storage/CoreseGraphDataManager.java @@ -2,14 +2,14 @@ import fr.inria.corese.core.Graph; import fr.inria.corese.core.producer.MetadataManager; -import fr.inria.corese.core.storage.api.dataManager.DataManager; -import fr.inria.corese.core.storage.api.dataManager.lifecycle.DataManagerLifecycle; -import fr.inria.corese.core.storage.api.dataManager.operations.BulkOperations; -import fr.inria.corese.core.storage.api.dataManager.operations.MetadataOperations; -import fr.inria.corese.core.storage.api.dataManager.operations.MutationOperations; -import fr.inria.corese.core.storage.api.dataManager.operations.QueryOperations; -import fr.inria.corese.core.storage.api.dataManager.transaction.IsolationLevel; -import fr.inria.corese.core.storage.api.dataManager.transaction.TransactionManager; +import fr.inria.corese.core.storage.api.datamanager.DataManager; +import fr.inria.corese.core.storage.api.datamanager.lifecycle.DataManagerLifecycle; +import fr.inria.corese.core.storage.api.datamanager.operations.BulkOperations; +import fr.inria.corese.core.storage.api.datamanager.operations.MetadataOperations; +import fr.inria.corese.core.storage.api.datamanager.operations.MutationOperations; +import fr.inria.corese.core.storage.api.datamanager.operations.QueryOperations; +import fr.inria.corese.core.storage.api.datamanager.transaction.IsolationLevel; +import fr.inria.corese.core.storage.api.datamanager.transaction.TransactionManager; import fr.inria.corese.core.storage.impl.lifecycle.LifecycleManagerImpl; import fr.inria.corese.core.storage.impl.operations.BulkOperationsImpl; import fr.inria.corese.core.storage.impl.operations.MetadataOperationsImpl; diff --git a/src/main/java/fr/inria/corese/core/storage/CoreseGraphDataManagerBuilder.java b/src/main/java/fr/inria/corese/core/storage/CoreseGraphDataManagerBuilder.java index 22513c993..556ae5dfa 100644 --- a/src/main/java/fr/inria/corese/core/storage/CoreseGraphDataManagerBuilder.java +++ b/src/main/java/fr/inria/corese/core/storage/CoreseGraphDataManagerBuilder.java @@ -1,7 +1,7 @@ package fr.inria.corese.core.storage; import fr.inria.corese.core.Graph; -import fr.inria.corese.core.storage.api.dataManager.DataManagerBuilder; +import fr.inria.corese.core.storage.api.datamanager.DataManagerBuilder; public class CoreseGraphDataManagerBuilder implements DataManagerBuilder { diff --git a/src/main/java/fr/inria/corese/core/storage/api/dataManager/lifecycle/DataManagerLifecycle.java b/src/main/java/fr/inria/corese/core/storage/api/dataManager/lifecycle/DataManagerLifecycle.java deleted file mode 100644 index 6d92dd303..000000000 --- a/src/main/java/fr/inria/corese/core/storage/api/dataManager/lifecycle/DataManagerLifecycle.java +++ /dev/null @@ -1,64 +0,0 @@ -package fr.inria.corese.core.storage.api.dataManager.lifecycle; - -import fr.inria.corese.core.storage.api.dataManager.support.config.DataManagerConfig; -import fr.inria.corese.core.storage.api.dataManager.support.exception.DataManagerException; - -/** - * Lifecycle management for the DataManager. - */ -public interface DataManagerLifecycle { - - /** - * Initializes the DataManager with the provided configuration. - * - * @param config DataManager configuration (must not be null) - * @throws DataManagerException if initialization fails - * @throws IllegalStateException if already initialized - * @throws IllegalArgumentException if config is null - */ - void initialize(DataManagerConfig config) throws DataManagerException; - - /** - * Checks if the DataManager is initialized and ready to use. - * - * @return true if initialized (RUNNING state), false otherwise - */ - boolean isInitialized(); - - /** - * Cleanly shuts down the DataManager and releases all resources. - * - * @throws DataManagerException if shutdown fails - * @throws IllegalStateException if not initialized - */ - void shutdown() throws DataManagerException; - - /** - * Restarts the DataManager with a new configuration. - * - * @param config New configuration - * @throws DataManagerException if restart fails - */ - default void restart(DataManagerConfig config) throws DataManagerException { - if (isInitialized()) { - shutdown(); - } - initialize(config); - } - - /** - * Returns the current lifecycle state. - * - * @return Current state - */ - LifecycleState getState(); - - /** - * Returns the currently used configuration. - * - * @return Current configuration, or null if not initialized - */ - DataManagerConfig getConfig(); - - -} \ No newline at end of file diff --git a/src/main/java/fr/inria/corese/core/storage/api/dataManager/DataManager.java b/src/main/java/fr/inria/corese/core/storage/api/datamanager/DataManager.java similarity index 98% rename from src/main/java/fr/inria/corese/core/storage/api/dataManager/DataManager.java rename to src/main/java/fr/inria/corese/core/storage/api/datamanager/DataManager.java index 2a5ee6f51..f0ead8374 100644 --- a/src/main/java/fr/inria/corese/core/storage/api/dataManager/DataManager.java +++ b/src/main/java/fr/inria/corese/core/storage/api/datamanager/DataManager.java @@ -1,4 +1,4 @@ -package fr.inria.corese.core.storage.api.dataManager; +package fr.inria.corese.core.storage.api.datamanager; import fr.inria.corese.core.producer.MetadataManager; import fr.inria.corese.core.sparql.triple.parser.HashMapList; diff --git a/src/main/java/fr/inria/corese/core/storage/api/dataManager/DataManagerBuilder.java b/src/main/java/fr/inria/corese/core/storage/api/datamanager/DataManagerBuilder.java similarity index 83% rename from src/main/java/fr/inria/corese/core/storage/api/dataManager/DataManagerBuilder.java rename to src/main/java/fr/inria/corese/core/storage/api/datamanager/DataManagerBuilder.java index 99b6e7fb6..58f38a898 100644 --- a/src/main/java/fr/inria/corese/core/storage/api/dataManager/DataManagerBuilder.java +++ b/src/main/java/fr/inria/corese/core/storage/api/datamanager/DataManagerBuilder.java @@ -1,4 +1,4 @@ -package fr.inria.corese.core.storage.api.dataManager; +package fr.inria.corese.core.storage.api.datamanager; /** * Builder for DataManager diff --git a/src/main/java/fr/inria/corese/core/storage/api/dataManager/DataManagerRead.java b/src/main/java/fr/inria/corese/core/storage/api/datamanager/DataManagerRead.java similarity index 98% rename from src/main/java/fr/inria/corese/core/storage/api/dataManager/DataManagerRead.java rename to src/main/java/fr/inria/corese/core/storage/api/datamanager/DataManagerRead.java index 05c7d53f9..593603bf2 100644 --- a/src/main/java/fr/inria/corese/core/storage/api/dataManager/DataManagerRead.java +++ b/src/main/java/fr/inria/corese/core/storage/api/datamanager/DataManagerRead.java @@ -1,4 +1,4 @@ -package fr.inria.corese.core.storage.api.dataManager; +package fr.inria.corese.core.storage.api.datamanager; import fr.inria.corese.core.kgram.api.core.Edge; import fr.inria.corese.core.kgram.api.core.Node; diff --git a/src/main/java/fr/inria/corese/core/storage/api/dataManager/DataManagerUpdate.java b/src/main/java/fr/inria/corese/core/storage/api/datamanager/DataManagerUpdate.java similarity index 99% rename from src/main/java/fr/inria/corese/core/storage/api/dataManager/DataManagerUpdate.java rename to src/main/java/fr/inria/corese/core/storage/api/datamanager/DataManagerUpdate.java index 3feb469ac..90cdab0ab 100644 --- a/src/main/java/fr/inria/corese/core/storage/api/dataManager/DataManagerUpdate.java +++ b/src/main/java/fr/inria/corese/core/storage/api/datamanager/DataManagerUpdate.java @@ -1,4 +1,4 @@ -package fr.inria.corese.core.storage.api.dataManager; +package fr.inria.corese.core.storage.api.datamanager; import java.util.HashSet; import java.util.List; diff --git a/src/main/java/fr/inria/corese/core/storage/api/datamanager/lifecycle/DataManagerLifecycle.java b/src/main/java/fr/inria/corese/core/storage/api/datamanager/lifecycle/DataManagerLifecycle.java new file mode 100644 index 000000000..b1e038ba9 --- /dev/null +++ b/src/main/java/fr/inria/corese/core/storage/api/datamanager/lifecycle/DataManagerLifecycle.java @@ -0,0 +1,94 @@ +package fr.inria.corese.core.storage.api.datamanager.lifecycle; + +import fr.inria.corese.core.storage.api.datamanager.support.config.DataManagerConfig; +import fr.inria.corese.core.storage.api.datamanager.support.exception.DataManagerException; +import fr.inria.corese.core.storage.api.datamanager.support.exception.ErrorCode; + +/** + * Lifecycle management for the DataManager. + */ +public interface DataManagerLifecycle { + + /** + * Initializes the DataManager with the provided configuration. + * + * @param config DataManager configuration (must not be null) + * @throws DataManagerException if initialization fails + * @throws IllegalStateException if already initialized + * @throws IllegalArgumentException if config is null + */ + void initialize(DataManagerConfig config) throws DataManagerException; + + /** + * Checks if the DataManager is initialized and ready to use. + * + * @return true if initialized (RUNNING state), false otherwise + */ + default boolean isInitialized() { + return getState().isRunnig(); + } + + /** + * Cleanly shuts down the DataManager and releases all resources. + * + * @throws DataManagerException if shutdown fails + * @throws IllegalStateException if not initialized + */ + void shutdown() throws DataManagerException; + + /** + * Restarts the DataManager with a new configuration. + * This method attempts to restore the previous configuration if the restart fails. + * However, restoration is not guaranteed if both shutdown and re-initialization fail. + * + * @param config New configuration (must not be null) + * @throws DataManagerException if restart fails + * @throws IllegalArgumentException if config is null + * @throws IllegalStateException if shutdown fails (when already initialized) + */ + default void restart(DataManagerConfig config) throws DataManagerException { + DataManagerConfig oldConfig = getConfig(); + boolean wasInitialized = isInitialized(); + + if (wasInitialized) { + shutdown(); + } + + try { + initialize(config); + } catch (DataManagerException e) { + if (wasInitialized && oldConfig != null) { + try { + initialize(oldConfig); + throw new DataManagerException( + ErrorCode.RESTART_FAILED_ROLLBACK_SUCCESS, + "Failed to restart with new config. Restored previous configuration.", + e); + } catch (DataManagerException rollbackEx) { + DataManagerException criticalFailure = new DataManagerException( + ErrorCode.RESTART_FAILED_ROLLBACK_FAILED, + "Failed to restart and unable to restore previous configuration.", + e); + criticalFailure.addSuppressed(rollbackEx); + throw criticalFailure; + } + } + throw e; + } + } + /** + * Returns the current lifecycle state. + * + * @return Current state + */ + LifecycleState getState(); + + /** + * Returns the currently used configuration. + * + * @return Current configuration, or null if not initialized + */ + DataManagerConfig getConfig(); + + +} \ No newline at end of file diff --git a/src/main/java/fr/inria/corese/core/storage/api/dataManager/lifecycle/LifecycleState.java b/src/main/java/fr/inria/corese/core/storage/api/datamanager/lifecycle/LifecycleState.java similarity index 90% rename from src/main/java/fr/inria/corese/core/storage/api/dataManager/lifecycle/LifecycleState.java rename to src/main/java/fr/inria/corese/core/storage/api/datamanager/lifecycle/LifecycleState.java index e812231fb..235ab351a 100644 --- a/src/main/java/fr/inria/corese/core/storage/api/dataManager/lifecycle/LifecycleState.java +++ b/src/main/java/fr/inria/corese/core/storage/api/datamanager/lifecycle/LifecycleState.java @@ -1,4 +1,4 @@ -package fr.inria.corese.core.storage.api.dataManager.lifecycle; +package fr.inria.corese.core.storage.api.datamanager.lifecycle; /** * Possible lifecycle states of the DataManager. @@ -44,7 +44,7 @@ public String getDescription() { * * @return true if usable (RUNNING) */ - public boolean isUsable() { + public boolean isRunnig() { return this == RUNNING; } } diff --git a/src/main/java/fr/inria/corese/core/storage/api/dataManager/operations/BulkOperations.java b/src/main/java/fr/inria/corese/core/storage/api/datamanager/operations/BulkOperations.java similarity index 77% rename from src/main/java/fr/inria/corese/core/storage/api/dataManager/operations/BulkOperations.java rename to src/main/java/fr/inria/corese/core/storage/api/datamanager/operations/BulkOperations.java index 69ac84361..c03d5dbb5 100644 --- a/src/main/java/fr/inria/corese/core/storage/api/dataManager/operations/BulkOperations.java +++ b/src/main/java/fr/inria/corese/core/storage/api/datamanager/operations/BulkOperations.java @@ -1,10 +1,10 @@ -package fr.inria.corese.core.storage.api.dataManager.operations; +package fr.inria.corese.core.storage.api.datamanager.operations; import fr.inria.corese.core.kgram.api.core.Edge; import fr.inria.corese.core.kgram.api.core.Node; -import fr.inria.corese.core.storage.api.dataManager.support.exception.DataManagerException; -import fr.inria.corese.core.storage.api.dataManager.support.model.EdgePattern; -import fr.inria.corese.core.storage.api.dataManager.support.model.MutationResult; +import fr.inria.corese.core.storage.api.datamanager.support.exception.DataManagerException; +import fr.inria.corese.core.storage.api.datamanager.support.model.EdgePattern; +import fr.inria.corese.core.storage.api.datamanager.support.model.MutationResult; import java.util.List; @@ -115,6 +115,18 @@ MutationResult moveGraph(Node sourceContext, Node targetContext, boolean silent) */ MutationResult declareContext(Node context) throws DataManagerException; + /** + * Declares (creates) multiple contexts in a single batch operation. + * More efficient than calling declareContext() multiple times. + * Useful for pre-creating multiple named graphs. + * + * @param contexts List of contexts to declare + * @return Bulk mutation result with statistics + * @throws DataManagerException if operation fails + * @throws IllegalArgumentException if contexts is null or empty + */ + MutationResult declareContexts(List contexts) throws DataManagerException; + /** * Undeclares (deletes) a context and all its edges. * @@ -125,4 +137,15 @@ MutationResult moveGraph(Node sourceContext, Node targetContext, boolean silent) */ MutationResult undeclareContext(Node context) throws DataManagerException; + /** + * Undeclares (deletes) multiple contexts and all their edges in a single batch operation. + * More efficient than calling undeclareContext() multiple times. + * + * @param contexts List of contexts to undeclare + * @return Bulk mutation result with all deleted edges + * @throws DataManagerException if operation fails + * @throws IllegalArgumentException if contexts is null or empty + */ + MutationResult undeclareContexts(List contexts) throws DataManagerException; + } \ No newline at end of file diff --git a/src/main/java/fr/inria/corese/core/storage/api/dataManager/operations/MetadataOperations.java b/src/main/java/fr/inria/corese/core/storage/api/datamanager/operations/MetadataOperations.java similarity index 91% rename from src/main/java/fr/inria/corese/core/storage/api/dataManager/operations/MetadataOperations.java rename to src/main/java/fr/inria/corese/core/storage/api/datamanager/operations/MetadataOperations.java index 770fc32b0..f6a76ce66 100644 --- a/src/main/java/fr/inria/corese/core/storage/api/dataManager/operations/MetadataOperations.java +++ b/src/main/java/fr/inria/corese/core/storage/api/datamanager/operations/MetadataOperations.java @@ -1,8 +1,8 @@ -package fr.inria.corese.core.storage.api.dataManager.operations; +package fr.inria.corese.core.storage.api.datamanager.operations; import fr.inria.corese.core.kgram.api.core.Node; -import fr.inria.corese.core.storage.api.dataManager.support.exception.DataManagerException; -import fr.inria.corese.core.storage.api.dataManager.support.model.GraphStatistics; +import fr.inria.corese.core.storage.api.datamanager.support.exception.DataManagerException; +import fr.inria.corese.core.storage.api.datamanager.support.model.GraphStatistics; import java.util.Set; /** diff --git a/src/main/java/fr/inria/corese/core/storage/api/dataManager/operations/MutationOperations.java b/src/main/java/fr/inria/corese/core/storage/api/datamanager/operations/MutationOperations.java similarity index 94% rename from src/main/java/fr/inria/corese/core/storage/api/dataManager/operations/MutationOperations.java rename to src/main/java/fr/inria/corese/core/storage/api/datamanager/operations/MutationOperations.java index 8374408ca..b7e3c8ee5 100644 --- a/src/main/java/fr/inria/corese/core/storage/api/dataManager/operations/MutationOperations.java +++ b/src/main/java/fr/inria/corese/core/storage/api/datamanager/operations/MutationOperations.java @@ -1,9 +1,9 @@ -package fr.inria.corese.core.storage.api.dataManager.operations; +package fr.inria.corese.core.storage.api.datamanager.operations; import fr.inria.corese.core.kgram.api.core.Edge; import fr.inria.corese.core.kgram.api.core.Node; -import fr.inria.corese.core.storage.api.dataManager.support.exception.DataManagerException; -import fr.inria.corese.core.storage.api.dataManager.support.model.MutationResult; +import fr.inria.corese.core.storage.api.datamanager.support.exception.DataManagerException; +import fr.inria.corese.core.storage.api.datamanager.support.model.MutationResult; import java.util.List; diff --git a/src/main/java/fr/inria/corese/core/storage/api/dataManager/operations/QueryOperations.java b/src/main/java/fr/inria/corese/core/storage/api/datamanager/operations/QueryOperations.java similarity index 91% rename from src/main/java/fr/inria/corese/core/storage/api/dataManager/operations/QueryOperations.java rename to src/main/java/fr/inria/corese/core/storage/api/datamanager/operations/QueryOperations.java index 48c0fe65c..925743257 100644 --- a/src/main/java/fr/inria/corese/core/storage/api/dataManager/operations/QueryOperations.java +++ b/src/main/java/fr/inria/corese/core/storage/api/datamanager/operations/QueryOperations.java @@ -1,8 +1,8 @@ -package fr.inria.corese.core.storage.api.dataManager.operations; +package fr.inria.corese.core.storage.api.datamanager.operations; import fr.inria.corese.core.kgram.api.core.Edge; -import fr.inria.corese.core.storage.api.dataManager.support.exception.DataManagerException; -import fr.inria.corese.core.storage.api.dataManager.support.model.EdgePattern; +import fr.inria.corese.core.storage.api.datamanager.support.exception.DataManagerException; +import fr.inria.corese.core.storage.api.datamanager.support.model.EdgePattern; import java.util.stream.Stream; diff --git a/src/main/java/fr/inria/corese/core/storage/api/dataManager/support/config/DataManagerConfig.java b/src/main/java/fr/inria/corese/core/storage/api/datamanager/support/config/DataManagerConfig.java similarity index 71% rename from src/main/java/fr/inria/corese/core/storage/api/dataManager/support/config/DataManagerConfig.java rename to src/main/java/fr/inria/corese/core/storage/api/datamanager/support/config/DataManagerConfig.java index 0de1c8631..45f3da571 100644 --- a/src/main/java/fr/inria/corese/core/storage/api/dataManager/support/config/DataManagerConfig.java +++ b/src/main/java/fr/inria/corese/core/storage/api/datamanager/support/config/DataManagerConfig.java @@ -1,4 +1,4 @@ -package fr.inria.corese.core.storage.api.dataManager.support.config; +package fr.inria.corese.core.storage.api.datamanager.support.config; import java.util.HashMap; import java.util.Map; @@ -63,7 +63,7 @@ public Map getProperties() { /** * Returns the storage path. * - * @return Storage path + * @return Storage path (never null) */ public String getStoragePath() { return storagePath; @@ -103,23 +103,28 @@ public String toString() { public static final class Builder { private final Map properties = new HashMap<>(); private boolean transactionSupport = false; - private String storagePath = "http://ns.inria.fr/corese/dataset"; + private String storagePath = null; private boolean debug = false; private Builder() { } /** - * Adds a generic property. + * Adds a custom property to the configuration. * - * @param key Property key - * @param value Property value - * @return This builder (for chaining) + * @param key Property key (must not be null, empty, or blank) + * @param value Property value (must not be null) + * @return this builder instance + * @throws IllegalArgumentException if key is null/empty/blank or value is null */ public Builder property(String key, Object value) { - if (key != null && value != null) { - properties.put(key, value); + if (key == null || key.isBlank()) { + throw new IllegalArgumentException("Property key cannot be null, empty, or blank"); + } + if (value == null) { + throw new IllegalArgumentException("Property value cannot be null"); } + this.properties.put(key, value); return this; } @@ -145,15 +150,18 @@ public Builder transactionSupport(boolean enable) { } /** - * Sets the storage path. + * Sets the storage path for the DataManager. + * This is a required configuration parameter. * - * @param path Storage path - * @return This builder (for chaining) + * @param path Storage path (must not be null, empty, or blank) + * @return this builder instance + * @throws IllegalArgumentException if path is null, empty, or blank */ public Builder storagePath(String path) { - if (path != null && !path.isEmpty()) { - this.storagePath = path; + if (path == null || path.isBlank()) { + throw new IllegalArgumentException("Storage path cannot be null, empty, or blank"); } + this.storagePath = path; return this; } @@ -172,8 +180,15 @@ public Builder debug(boolean debug) { * Builds the DataManagerConfig instance. * * @return New configured instance + * @throws IllegalStateException if storagePath has not been set */ public DataManagerConfig build() { + if (storagePath == null || storagePath.isBlank()) { + throw new IllegalStateException( + "Storage path must be set before building configuration. " + + "Use storagePath(String) to specify the storage location." + ); + } return new DataManagerConfig(this); } } diff --git a/src/main/java/fr/inria/corese/core/storage/api/dataManager/support/exception/DataManagerException.java b/src/main/java/fr/inria/corese/core/storage/api/datamanager/support/exception/DataManagerException.java similarity index 94% rename from src/main/java/fr/inria/corese/core/storage/api/dataManager/support/exception/DataManagerException.java rename to src/main/java/fr/inria/corese/core/storage/api/datamanager/support/exception/DataManagerException.java index 4eb70da90..f0490b5e4 100644 --- a/src/main/java/fr/inria/corese/core/storage/api/dataManager/support/exception/DataManagerException.java +++ b/src/main/java/fr/inria/corese/core/storage/api/datamanager/support/exception/DataManagerException.java @@ -1,4 +1,4 @@ -package fr.inria.corese.core.storage.api.dataManager.support.exception; +package fr.inria.corese.core.storage.api.datamanager.support.exception; /** * Specific exception for DataManager operations. diff --git a/src/main/java/fr/inria/corese/core/storage/api/dataManager/support/exception/ErrorCode.java b/src/main/java/fr/inria/corese/core/storage/api/datamanager/support/exception/ErrorCode.java similarity index 71% rename from src/main/java/fr/inria/corese/core/storage/api/dataManager/support/exception/ErrorCode.java rename to src/main/java/fr/inria/corese/core/storage/api/datamanager/support/exception/ErrorCode.java index e33189555..59b4fa453 100644 --- a/src/main/java/fr/inria/corese/core/storage/api/dataManager/support/exception/ErrorCode.java +++ b/src/main/java/fr/inria/corese/core/storage/api/datamanager/support/exception/ErrorCode.java @@ -1,4 +1,4 @@ -package fr.inria.corese.core.storage.api.dataManager.support.exception; +package fr.inria.corese.core.storage.api.datamanager.support.exception; /** * Error codes for DataManager operations. @@ -29,7 +29,14 @@ public enum ErrorCode { UNSUPPORTED_OPERATION("UNSUPPORTED", "Operation not supported"), /** Invalid state */ - INVALID_STATE("INVALID_STATE", "Invalid state"); + INVALID_STATE("INVALID_STATE", "Invalid state"), + + RESTART_FAILED_ROLLBACK_SUCCESS("RESTART_FAIL_ROLLBACK_OK", + "Restart failed but previous configuration restored"), + + /** Restart failed and rollback also failed (critical) */ + RESTART_FAILED_ROLLBACK_FAILED("RESTART_FAIL_ROLLBACK_FAIL", + "Restart failed and unable to restore previous configuration"),; private final String code; private final String description; diff --git a/src/main/java/fr/inria/corese/core/storage/api/dataManager/support/model/EdgePattern.java b/src/main/java/fr/inria/corese/core/storage/api/datamanager/support/model/EdgePattern.java similarity index 98% rename from src/main/java/fr/inria/corese/core/storage/api/dataManager/support/model/EdgePattern.java rename to src/main/java/fr/inria/corese/core/storage/api/datamanager/support/model/EdgePattern.java index 6c6295584..4cb06a495 100644 --- a/src/main/java/fr/inria/corese/core/storage/api/dataManager/support/model/EdgePattern.java +++ b/src/main/java/fr/inria/corese/core/storage/api/datamanager/support/model/EdgePattern.java @@ -1,4 +1,4 @@ -package fr.inria.corese.core.storage.api.dataManager.support.model; +package fr.inria.corese.core.storage.api.datamanager.support.model; import fr.inria.corese.core.kgram.api.core.Node; diff --git a/src/main/java/fr/inria/corese/core/storage/api/dataManager/support/model/GraphStatistics.java b/src/main/java/fr/inria/corese/core/storage/api/datamanager/support/model/GraphStatistics.java similarity index 98% rename from src/main/java/fr/inria/corese/core/storage/api/dataManager/support/model/GraphStatistics.java rename to src/main/java/fr/inria/corese/core/storage/api/datamanager/support/model/GraphStatistics.java index 5a7717abd..9d77c1ace 100644 --- a/src/main/java/fr/inria/corese/core/storage/api/dataManager/support/model/GraphStatistics.java +++ b/src/main/java/fr/inria/corese/core/storage/api/datamanager/support/model/GraphStatistics.java @@ -1,4 +1,4 @@ -package fr.inria.corese.core.storage.api.dataManager.support.model; +package fr.inria.corese.core.storage.api.datamanager.support.model; import java.util.Objects; diff --git a/src/main/java/fr/inria/corese/core/storage/api/dataManager/support/model/MutationResult.java b/src/main/java/fr/inria/corese/core/storage/api/datamanager/support/model/MutationResult.java similarity index 99% rename from src/main/java/fr/inria/corese/core/storage/api/dataManager/support/model/MutationResult.java rename to src/main/java/fr/inria/corese/core/storage/api/datamanager/support/model/MutationResult.java index 794f2a0fa..d6e2d9ab7 100644 --- a/src/main/java/fr/inria/corese/core/storage/api/dataManager/support/model/MutationResult.java +++ b/src/main/java/fr/inria/corese/core/storage/api/datamanager/support/model/MutationResult.java @@ -1,4 +1,4 @@ -package fr.inria.corese.core.storage.api.dataManager.support.model; +package fr.inria.corese.core.storage.api.datamanager.support.model; import fr.inria.corese.core.kgram.api.core.Edge; diff --git a/src/main/java/fr/inria/corese/core/storage/api/dataManager/transaction/IsolationLevel.java b/src/main/java/fr/inria/corese/core/storage/api/datamanager/transaction/IsolationLevel.java similarity index 92% rename from src/main/java/fr/inria/corese/core/storage/api/dataManager/transaction/IsolationLevel.java rename to src/main/java/fr/inria/corese/core/storage/api/datamanager/transaction/IsolationLevel.java index 8296c30b3..7f5c07887 100644 --- a/src/main/java/fr/inria/corese/core/storage/api/dataManager/transaction/IsolationLevel.java +++ b/src/main/java/fr/inria/corese/core/storage/api/datamanager/transaction/IsolationLevel.java @@ -1,4 +1,4 @@ -package fr.inria.corese.core.storage.api.dataManager.transaction; +package fr.inria.corese.core.storage.api.datamanager.transaction; /** * Transaction isolation levels. diff --git a/src/main/java/fr/inria/corese/core/storage/api/dataManager/transaction/Transaction.java b/src/main/java/fr/inria/corese/core/storage/api/datamanager/transaction/Transaction.java similarity index 92% rename from src/main/java/fr/inria/corese/core/storage/api/dataManager/transaction/Transaction.java rename to src/main/java/fr/inria/corese/core/storage/api/datamanager/transaction/Transaction.java index cc3b10774..7a14c7b2a 100644 --- a/src/main/java/fr/inria/corese/core/storage/api/dataManager/transaction/Transaction.java +++ b/src/main/java/fr/inria/corese/core/storage/api/datamanager/transaction/Transaction.java @@ -1,6 +1,6 @@ -package fr.inria.corese.core.storage.api.dataManager.transaction; +package fr.inria.corese.core.storage.api.datamanager.transaction; -import fr.inria.corese.core.storage.api.dataManager.support.exception.DataManagerException; +import fr.inria.corese.core.storage.api.datamanager.support.exception.DataManagerException; /** * Handle representing an active transaction. diff --git a/src/main/java/fr/inria/corese/core/storage/api/dataManager/transaction/TransactionManager.java b/src/main/java/fr/inria/corese/core/storage/api/datamanager/transaction/TransactionManager.java similarity index 94% rename from src/main/java/fr/inria/corese/core/storage/api/dataManager/transaction/TransactionManager.java rename to src/main/java/fr/inria/corese/core/storage/api/datamanager/transaction/TransactionManager.java index 87f2e6323..a67dc3e09 100644 --- a/src/main/java/fr/inria/corese/core/storage/api/dataManager/transaction/TransactionManager.java +++ b/src/main/java/fr/inria/corese/core/storage/api/datamanager/transaction/TransactionManager.java @@ -1,6 +1,6 @@ -package fr.inria.corese.core.storage.api.dataManager.transaction; +package fr.inria.corese.core.storage.api.datamanager.transaction; -import fr.inria.corese.core.storage.api.dataManager.support.exception.DataManagerException; +import fr.inria.corese.core.storage.api.datamanager.support.exception.DataManagerException; import java.util.Optional; import java.util.Set; diff --git a/src/main/java/fr/inria/corese/core/storage/api/dataManager/transaction/TransactionState.java b/src/main/java/fr/inria/corese/core/storage/api/datamanager/transaction/TransactionState.java similarity index 91% rename from src/main/java/fr/inria/corese/core/storage/api/dataManager/transaction/TransactionState.java rename to src/main/java/fr/inria/corese/core/storage/api/datamanager/transaction/TransactionState.java index de2bbda4b..318557cc7 100644 --- a/src/main/java/fr/inria/corese/core/storage/api/dataManager/transaction/TransactionState.java +++ b/src/main/java/fr/inria/corese/core/storage/api/datamanager/transaction/TransactionState.java @@ -1,4 +1,4 @@ -package fr.inria.corese.core.storage.api.dataManager.transaction; +package fr.inria.corese.core.storage.api.datamanager.transaction; public enum TransactionState { /** diff --git a/src/main/java/fr/inria/corese/core/storage/impl/lifecycle/LifecycleManagerImpl.java b/src/main/java/fr/inria/corese/core/storage/impl/lifecycle/LifecycleManagerImpl.java index 1cf2c88de..ee9d3600f 100644 --- a/src/main/java/fr/inria/corese/core/storage/impl/lifecycle/LifecycleManagerImpl.java +++ b/src/main/java/fr/inria/corese/core/storage/impl/lifecycle/LifecycleManagerImpl.java @@ -1,11 +1,11 @@ package fr.inria.corese.core.storage.impl.lifecycle; import fr.inria.corese.core.Graph; -import fr.inria.corese.core.storage.api.dataManager.lifecycle.DataManagerLifecycle; -import fr.inria.corese.core.storage.api.dataManager.lifecycle.LifecycleState; -import fr.inria.corese.core.storage.api.dataManager.support.config.DataManagerConfig; -import fr.inria.corese.core.storage.api.dataManager.support.exception.DataManagerException; -import fr.inria.corese.core.storage.api.dataManager.support.exception.ErrorCode; +import fr.inria.corese.core.storage.api.datamanager.lifecycle.DataManagerLifecycle; +import fr.inria.corese.core.storage.api.datamanager.lifecycle.LifecycleState; +import fr.inria.corese.core.storage.api.datamanager.support.config.DataManagerConfig; +import fr.inria.corese.core.storage.api.datamanager.support.exception.DataManagerException; +import fr.inria.corese.core.storage.api.datamanager.support.exception.ErrorCode; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -197,7 +197,7 @@ private void applyConfiguration(DataManagerConfig config) { * @throws IllegalStateException if the current state is not usable (e.g., NOT_INITIALIZED or SHUTDOWN). */ public void checkUsable() { - if (!state.isUsable()) { + if (!state.isRunnig()) { throw new IllegalStateException( "DataManager is not in a usable state. Current state: " + state ); diff --git a/src/main/java/fr/inria/corese/core/storage/impl/operations/BulkOperationsImpl.java b/src/main/java/fr/inria/corese/core/storage/impl/operations/BulkOperationsImpl.java index e69de0ca4..4277612a5 100644 --- a/src/main/java/fr/inria/corese/core/storage/impl/operations/BulkOperationsImpl.java +++ b/src/main/java/fr/inria/corese/core/storage/impl/operations/BulkOperationsImpl.java @@ -3,11 +3,11 @@ import fr.inria.corese.core.Graph; import fr.inria.corese.core.kgram.api.core.Edge; import fr.inria.corese.core.kgram.api.core.Node; -import fr.inria.corese.core.storage.api.dataManager.operations.BulkOperations; -import fr.inria.corese.core.storage.api.dataManager.support.exception.DataManagerException; -import fr.inria.corese.core.storage.api.dataManager.support.exception.ErrorCode; -import fr.inria.corese.core.storage.api.dataManager.support.model.EdgePattern; -import fr.inria.corese.core.storage.api.dataManager.support.model.MutationResult; +import fr.inria.corese.core.storage.api.datamanager.operations.BulkOperations; +import fr.inria.corese.core.storage.api.datamanager.support.exception.DataManagerException; +import fr.inria.corese.core.storage.api.datamanager.support.exception.ErrorCode; +import fr.inria.corese.core.storage.api.datamanager.support.model.EdgePattern; +import fr.inria.corese.core.storage.api.datamanager.support.model.MutationResult; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -150,6 +150,7 @@ public MutationResult deleteBatch(List edges) throws DataManagerException * @param pattern the pattern defining subject, predicate, object, and/or context filters. * @return a {@link MutationResult} containing the list of affected edges. * @throws DataManagerException if the pattern-based deletion fails. + * @throws IllegalArgumentException if pattern is null. */ @Override public MutationResult deleteByPattern(EdgePattern pattern) throws DataManagerException { @@ -298,6 +299,7 @@ public MutationResult clearAll() throws DataManagerException { * @param silent if true, ignores missing source graphs. * @return a {@link MutationResult} representing the merge outcome. * @throws DataManagerException if the operation fails at the graph level. + * @throws IllegalArgumentException if contexts are null. */ @Override public MutationResult addGraph(Node sourceContext, Node targetContext, boolean silent) @@ -348,6 +350,7 @@ public MutationResult addGraph(Node sourceContext, Node targetContext, boolean s * @param silent if true, ignores missing source graphs. * @return a {@link MutationResult} representing the copy outcome. * @throws DataManagerException if the operation fails at the graph level. + * @throws IllegalArgumentException if contexts are null. */ @Override public MutationResult copyGraph(Node sourceContext, Node targetContext, boolean silent) @@ -398,6 +401,7 @@ public MutationResult copyGraph(Node sourceContext, Node targetContext, boolean * @param silent if true, ignores missing source graphs. * @return a {@link MutationResult} representing the move outcome. * @throws DataManagerException if the operation fails at the graph level. + * @throws IllegalArgumentException if contexts are null. */ @Override public MutationResult moveGraph(Node sourceContext, Node targetContext, boolean silent) @@ -446,6 +450,7 @@ public MutationResult moveGraph(Node sourceContext, Node targetContext, boolean * @param context the context node to declare. * @return a successful {@link MutationResult}. * @throws DataManagerException if the declaration fails. + * @throws IllegalArgumentException if context is null. */ @Override public MutationResult declareContext(Node context) throws DataManagerException { @@ -470,12 +475,58 @@ public MutationResult declareContext(Node context) throws DataManagerException { } } + /** + * Declares multiple contexts in a single batch operation. + * + * @param contexts the list of context nodes to declare; must not be null or empty. + * @return a {@link MutationResult} summarizing the successes and failures. + * @throws DataManagerException if a critical system error occurs during declaration. + * @throws IllegalArgumentException if contexts is null or empty. + */ + @Override + public MutationResult declareContexts(List contexts) throws DataManagerException { + if (contexts == null || contexts.isEmpty()) { + throw new IllegalArgumentException("Contexts list cannot be null or empty"); + } + + logger.info("Declaring batch of {} context(s)", contexts.size()); + + MutationResult.BulkBuilder builder = MutationResult.bulkBuilder() + .totalAttempted(contexts.size()) + .message("Batch declare of " + contexts.size() + " context(s)"); + + try { + for (Node context : contexts) { + try { + graph.addGraphNode(context); + builder.incrementSuccess(); + } catch (Exception e) { + builder.addFailure(null, "Failed to declare context " + context + ": " + e.getMessage(), e); + } + } + + MutationResult result = builder.build(); + logger.info("Batch declare completed: success={}, failure={}", + result.getSuccessCount(), result.getFailureCount()); + return result; + + } catch (Exception e) { + logger.error("Batch declare contexts failed", e); + throw new DataManagerException( + ErrorCode.MUTATION_FAILED, + "Batch declare contexts failed: " + e.getMessage(), + e + ); + } + } + /** * Removes a context declaration and all its associated edges. * * @param context the context node to undeclare. * @return a {@link MutationResult} summarizing the removed edges. * @throws DataManagerException if the undeclare operation fails. + * @throws IllegalArgumentException if context is null. */ @Override public MutationResult undeclareContext(Node context) throws DataManagerException { @@ -498,4 +549,34 @@ public MutationResult undeclareContext(Node context) throws DataManagerException ); } } + + /** + * Undeclares multiple contexts and all their edges in a single batch operation. + * + * @param contexts the list of context nodes to undeclare; must not be null or empty. + * @return a {@link MutationResult} summarizing all removed edges. + * @throws DataManagerException if a critical system error occurs during undeclaration. + * @throws IllegalArgumentException if contexts is null or empty. + */ + @Override + public MutationResult undeclareContexts(List contexts) throws DataManagerException { + if (contexts == null || contexts.isEmpty()) { + throw new IllegalArgumentException("Contexts list cannot be null or empty"); + } + + logger.info("Undeclaring batch of {} context(s)", contexts.size()); + + try { + // Delegate to clearContexts which already handles batch operations + return clearContexts(contexts, false); + + } catch (Exception e) { + logger.error("Batch undeclare contexts failed", e); + throw new DataManagerException( + ErrorCode.MUTATION_FAILED, + "Batch undeclare contexts failed: " + e.getMessage(), + e + ); + } + } } \ No newline at end of file diff --git a/src/main/java/fr/inria/corese/core/storage/impl/operations/MetadataOperationsImpl.java b/src/main/java/fr/inria/corese/core/storage/impl/operations/MetadataOperationsImpl.java index ead905f80..81261d27c 100644 --- a/src/main/java/fr/inria/corese/core/storage/impl/operations/MetadataOperationsImpl.java +++ b/src/main/java/fr/inria/corese/core/storage/impl/operations/MetadataOperationsImpl.java @@ -2,10 +2,10 @@ import fr.inria.corese.core.Graph; import fr.inria.corese.core.kgram.api.core.Node; -import fr.inria.corese.core.storage.api.dataManager.operations.MetadataOperations; -import fr.inria.corese.core.storage.api.dataManager.support.exception.DataManagerException; -import fr.inria.corese.core.storage.api.dataManager.support.exception.ErrorCode; -import fr.inria.corese.core.storage.api.dataManager.support.model.GraphStatistics; +import fr.inria.corese.core.storage.api.datamanager.operations.MetadataOperations; +import fr.inria.corese.core.storage.api.datamanager.support.exception.DataManagerException; +import fr.inria.corese.core.storage.api.datamanager.support.exception.ErrorCode; +import fr.inria.corese.core.storage.api.datamanager.support.model.GraphStatistics; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/src/main/java/fr/inria/corese/core/storage/impl/operations/MutationOperationsImpl.java b/src/main/java/fr/inria/corese/core/storage/impl/operations/MutationOperationsImpl.java index 61be81e1a..97e4c146f 100644 --- a/src/main/java/fr/inria/corese/core/storage/impl/operations/MutationOperationsImpl.java +++ b/src/main/java/fr/inria/corese/core/storage/impl/operations/MutationOperationsImpl.java @@ -3,10 +3,10 @@ import fr.inria.corese.core.Graph; import fr.inria.corese.core.kgram.api.core.Edge; import fr.inria.corese.core.kgram.api.core.Node; -import fr.inria.corese.core.storage.api.dataManager.operations.MutationOperations; -import fr.inria.corese.core.storage.api.dataManager.support.exception.DataManagerException; -import fr.inria.corese.core.storage.api.dataManager.support.exception.ErrorCode; -import fr.inria.corese.core.storage.api.dataManager.support.model.MutationResult; +import fr.inria.corese.core.storage.api.datamanager.operations.MutationOperations; +import fr.inria.corese.core.storage.api.datamanager.support.exception.DataManagerException; +import fr.inria.corese.core.storage.api.datamanager.support.exception.ErrorCode; +import fr.inria.corese.core.storage.api.datamanager.support.model.MutationResult; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/src/main/java/fr/inria/corese/core/storage/impl/operations/QueryOperationsImpl.java b/src/main/java/fr/inria/corese/core/storage/impl/operations/QueryOperationsImpl.java index fffd69a48..0c90e8127 100644 --- a/src/main/java/fr/inria/corese/core/storage/impl/operations/QueryOperationsImpl.java +++ b/src/main/java/fr/inria/corese/core/storage/impl/operations/QueryOperationsImpl.java @@ -3,10 +3,10 @@ import fr.inria.corese.core.Graph; import fr.inria.corese.core.kgram.api.core.Edge; import fr.inria.corese.core.kgram.api.core.Node; -import fr.inria.corese.core.storage.api.dataManager.support.exception.ErrorCode; -import fr.inria.corese.core.storage.api.dataManager.support.model.EdgePattern; -import fr.inria.corese.core.storage.api.dataManager.operations.QueryOperations; -import fr.inria.corese.core.storage.api.dataManager.support.exception.DataManagerException; +import fr.inria.corese.core.storage.api.datamanager.support.exception.ErrorCode; +import fr.inria.corese.core.storage.api.datamanager.support.model.EdgePattern; +import fr.inria.corese.core.storage.api.datamanager.operations.QueryOperations; +import fr.inria.corese.core.storage.api.datamanager.support.exception.DataManagerException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/src/main/java/fr/inria/corese/core/storage/impl/transaction/TransactionImpl.java b/src/main/java/fr/inria/corese/core/storage/impl/transaction/TransactionImpl.java index 5d83f9e5e..1753bdada 100644 --- a/src/main/java/fr/inria/corese/core/storage/impl/transaction/TransactionImpl.java +++ b/src/main/java/fr/inria/corese/core/storage/impl/transaction/TransactionImpl.java @@ -1,10 +1,10 @@ package fr.inria.corese.core.storage.impl.transaction; -import fr.inria.corese.core.storage.api.dataManager.support.exception.DataManagerException; -import fr.inria.corese.core.storage.api.dataManager.support.exception.ErrorCode; -import fr.inria.corese.core.storage.api.dataManager.transaction.IsolationLevel; -import fr.inria.corese.core.storage.api.dataManager.transaction.Transaction; -import fr.inria.corese.core.storage.api.dataManager.transaction.TransactionState; +import fr.inria.corese.core.storage.api.datamanager.support.exception.DataManagerException; +import fr.inria.corese.core.storage.api.datamanager.support.exception.ErrorCode; +import fr.inria.corese.core.storage.api.datamanager.transaction.IsolationLevel; +import fr.inria.corese.core.storage.api.datamanager.transaction.Transaction; +import fr.inria.corese.core.storage.api.datamanager.transaction.TransactionState; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/src/main/java/fr/inria/corese/core/storage/impl/transaction/TransactionManagerImpl.java b/src/main/java/fr/inria/corese/core/storage/impl/transaction/TransactionManagerImpl.java index ef74416ce..1ff599a1c 100644 --- a/src/main/java/fr/inria/corese/core/storage/impl/transaction/TransactionManagerImpl.java +++ b/src/main/java/fr/inria/corese/core/storage/impl/transaction/TransactionManagerImpl.java @@ -1,11 +1,11 @@ package fr.inria.corese.core.storage.impl.transaction; import fr.inria.corese.core.Graph; -import fr.inria.corese.core.storage.api.dataManager.support.exception.DataManagerException; -import fr.inria.corese.core.storage.api.dataManager.support.exception.ErrorCode; -import fr.inria.corese.core.storage.api.dataManager.transaction.IsolationLevel; -import fr.inria.corese.core.storage.api.dataManager.transaction.Transaction; -import fr.inria.corese.core.storage.api.dataManager.transaction.TransactionManager; +import fr.inria.corese.core.storage.api.datamanager.support.exception.DataManagerException; +import fr.inria.corese.core.storage.api.datamanager.support.exception.ErrorCode; +import fr.inria.corese.core.storage.api.datamanager.transaction.IsolationLevel; +import fr.inria.corese.core.storage.api.datamanager.transaction.Transaction; +import fr.inria.corese.core.storage.api.datamanager.transaction.TransactionManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/src/main/java/fr/inria/corese/core/transform/Transformer.java b/src/main/java/fr/inria/corese/core/transform/Transformer.java index 0b2cf3770..0b090b5a5 100644 --- a/src/main/java/fr/inria/corese/core/transform/Transformer.java +++ b/src/main/java/fr/inria/corese/core/transform/Transformer.java @@ -25,7 +25,7 @@ import fr.inria.corese.core.sparql.triple.parser.*; import fr.inria.corese.core.sparql.triple.parser.Access.Feature; import fr.inria.corese.core.sparql.triple.parser.Access.Level; -import fr.inria.corese.core.storage.api.dataManager.DataManager; +import fr.inria.corese.core.storage.api.datamanager.DataManager; import fr.inria.corese.core.visitor.solver.QuerySolverVisitorTransformer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/src/main/java/fr/inria/corese/core/workflow/Data.java b/src/main/java/fr/inria/corese/core/workflow/Data.java index 914c6c3a8..9f908dc25 100644 --- a/src/main/java/fr/inria/corese/core/workflow/Data.java +++ b/src/main/java/fr/inria/corese/core/workflow/Data.java @@ -10,7 +10,7 @@ import fr.inria.corese.core.sparql.triple.function.term.Binding; import fr.inria.corese.core.sparql.triple.parser.Context; import fr.inria.corese.core.sparql.triple.parser.Dataset; -import fr.inria.corese.core.storage.api.dataManager.DataManager; +import fr.inria.corese.core.storage.api.datamanager.DataManager; import fr.inria.corese.core.transform.TemplateVisitor; import fr.inria.corese.core.visitor.solver.QuerySolverVisitorTransformer; diff --git a/src/test/java/fr/inria/corese/core/storage/CoreseGraphDataManagerIntegrationTest.java b/src/test/java/fr/inria/corese/core/storage/CoreseGraphDataManagerIntegrationTest.java index 0b613ecca..8cf5cd82e 100644 --- a/src/test/java/fr/inria/corese/core/storage/CoreseGraphDataManagerIntegrationTest.java +++ b/src/test/java/fr/inria/corese/core/storage/CoreseGraphDataManagerIntegrationTest.java @@ -2,19 +2,20 @@ import fr.inria.corese.core.kgram.api.core.Edge; import fr.inria.corese.core.kgram.api.core.Node; -import fr.inria.corese.core.storage.api.dataManager.support.config.DataManagerConfig; -import fr.inria.corese.core.storage.api.dataManager.support.exception.DataManagerException; -import fr.inria.corese.core.storage.api.dataManager.support.model.EdgePattern; -import fr.inria.corese.core.storage.api.dataManager.support.model.GraphStatistics; -import fr.inria.corese.core.storage.api.dataManager.support.model.MutationResult; +import fr.inria.corese.core.storage.api.datamanager.support.config.DataManagerConfig; +import fr.inria.corese.core.storage.api.datamanager.support.exception.DataManagerException; +import fr.inria.corese.core.storage.api.datamanager.support.model.EdgePattern; +import fr.inria.corese.core.storage.api.datamanager.support.model.GraphStatistics; +import fr.inria.corese.core.storage.api.datamanager.support.model.MutationResult; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; import java.util.Set; import java.util.stream.Stream; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * Integration tests for CoresGraphDataManager. @@ -26,15 +27,14 @@ class CoreseGraphDataManagerIntegrationTest { private CoreseGraphDataManager dataManager; @BeforeEach - void setUp() throws DataManagerException { + void setUp() { dataManager = new CoreseGraphDataManagerBuilder().build(); // Initialize with transaction support enabled DataManagerConfig config = DataManagerConfig.builder() - .debug(false) - .transactionSupport(true) + .storagePath("http://ns.inria.fr/corese/my-dataset") + .debug(true) .build(); - dataManager.getLifecycle().initialize(config); } @Test @@ -124,13 +124,5 @@ void testClearAllData() throws DataManagerException { assertEquals(0, dataManager.getGraph().size()); } - @Test - @DisplayName("Lifecycle shutdown should properly clean up resources") - void testLifecycleShutdown() throws DataManagerException { - assertTrue(dataManager.getLifecycle().isInitialized()); - dataManager.getLifecycle().shutdown(); - - assertFalse(dataManager.getLifecycle().isInitialized()); - } } \ No newline at end of file diff --git a/src/test/java/fr/inria/corese/core/storage/impl/lifecycle/LifecycleManagerImplTest.java b/src/test/java/fr/inria/corese/core/storage/impl/lifecycle/LifecycleManagerImplTest.java index e38ffbfeb..3972c7f56 100644 --- a/src/test/java/fr/inria/corese/core/storage/impl/lifecycle/LifecycleManagerImplTest.java +++ b/src/test/java/fr/inria/corese/core/storage/impl/lifecycle/LifecycleManagerImplTest.java @@ -1,9 +1,9 @@ package fr.inria.corese.core.storage.impl.lifecycle; import fr.inria.corese.core.Graph; -import fr.inria.corese.core.storage.api.dataManager.lifecycle.LifecycleState; -import fr.inria.corese.core.storage.api.dataManager.support.config.DataManagerConfig; -import fr.inria.corese.core.storage.api.dataManager.support.exception.DataManagerException; +import fr.inria.corese.core.storage.api.datamanager.lifecycle.LifecycleState; +import fr.inria.corese.core.storage.api.datamanager.support.config.DataManagerConfig; +import fr.inria.corese.core.storage.api.datamanager.support.exception.DataManagerException; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -16,6 +16,8 @@ @DisplayName("LifecycleManagerImpl Tests") class LifecycleManagerImplTest { + private static final String TEST_STORAGE_PATH = "http://ns.inria.fr/corese/test-dataset"; + private LifecycleManagerImpl lifecycleManager; @BeforeEach @@ -24,6 +26,25 @@ void setUp() { lifecycleManager = new LifecycleManagerImpl(graph); } + /** + * Helper method to create a default test configuration. + */ + private DataManagerConfig createDefaultConfig() { + return DataManagerConfig.builder() + .storagePath(TEST_STORAGE_PATH) + .build(); + } + + /** + * Helper method to create a test configuration with debug enabled. + */ + private DataManagerConfig createDebugConfig() { + return DataManagerConfig.builder() + .storagePath(TEST_STORAGE_PATH) + .debug(true) + .build(); + } + @Test @DisplayName("Initial state should be NOT_INITIALIZED") void testInitialStateIsNotInitialized() { @@ -34,10 +55,7 @@ void testInitialStateIsNotInitialized() { @Test @DisplayName("Initialize with valid config should succeed") void testInitializeSuccess() throws DataManagerException { - DataManagerConfig config = DataManagerConfig.builder() - .debug(true) - .build(); - + DataManagerConfig config = createDebugConfig(); lifecycleManager.initialize(config); assertEquals(LifecycleState.RUNNING, lifecycleManager.getState()); @@ -54,7 +72,7 @@ void testInitializeWithNullConfigThrows() { @Test @DisplayName("Shutdown after initialization should succeed") void testShutdownSuccess() throws DataManagerException { - DataManagerConfig config = DataManagerConfig.builder().build(); + DataManagerConfig config = createDefaultConfig(); lifecycleManager.initialize(config); lifecycleManager.shutdown(); @@ -63,14 +81,16 @@ void testShutdownSuccess() throws DataManagerException { assertFalse(lifecycleManager.isInitialized()); } - @Test @DisplayName("Restart should reinitialize successfully") void testRestartSuccess() throws DataManagerException { - DataManagerConfig config1 = DataManagerConfig.builder().debug(false).build(); + DataManagerConfig config1 = DataManagerConfig.builder() + .storagePath(TEST_STORAGE_PATH) + .debug(false) + .build(); lifecycleManager.initialize(config1); - DataManagerConfig config2 = DataManagerConfig.builder().debug(true).build(); + DataManagerConfig config2 = createDebugConfig(); lifecycleManager.restart(config2); assertEquals(LifecycleState.RUNNING, lifecycleManager.getState()); @@ -80,7 +100,7 @@ void testRestartSuccess() throws DataManagerException { @Test @DisplayName("checkUsable when RUNNING should not throw") void testCheckUsableWhenRunning() throws DataManagerException { - DataManagerConfig config = DataManagerConfig.builder().build(); + DataManagerConfig config = createDefaultConfig(); lifecycleManager.initialize(config); // Should not throw @@ -96,7 +116,7 @@ void testCheckUsableWhenNotInitialized() { @Test @DisplayName("checkUsable after shutdown should throw IllegalStateException") void testCheckUsableAfterShutdown() throws DataManagerException { - DataManagerConfig config = DataManagerConfig.builder().build(); + DataManagerConfig config = createDefaultConfig(); lifecycleManager.initialize(config); lifecycleManager.shutdown(); @@ -115,12 +135,14 @@ void testInitializeWithCustomStoragePath() throws DataManagerException { assertTrue(lifecycleManager.isInitialized()); assertEquals(LifecycleState.RUNNING, lifecycleManager.getState()); + assertEquals(customPath, config.getStoragePath()); } @Test @DisplayName("Initialize with transaction support enabled") void testInitializeWithTransactionSupport() throws DataManagerException { DataManagerConfig config = DataManagerConfig.builder() + .storagePath(TEST_STORAGE_PATH) .transactionSupport(true) .build(); @@ -133,6 +155,7 @@ void testInitializeWithTransactionSupport() throws DataManagerException { @DisplayName("Initialize with custom properties") void testInitializeWithCustomProperties() throws DataManagerException { DataManagerConfig config = DataManagerConfig.builder() + .storagePath(TEST_STORAGE_PATH) .property("custom.key", "custom.value") .property("another.key", "another.value") .build(); @@ -146,11 +169,15 @@ void testInitializeWithCustomProperties() throws DataManagerException { @Test @DisplayName("Multiple restarts should work correctly") void testMultipleRestarts() throws DataManagerException { - DataManagerConfig config1 = DataManagerConfig.builder().debug(false).build(); + DataManagerConfig config1 = DataManagerConfig.builder() + .storagePath(TEST_STORAGE_PATH) + .debug(false) + .build(); lifecycleManager.initialize(config1); for (int i = 0; i < 3; i++) { DataManagerConfig config = DataManagerConfig.builder() + .storagePath(TEST_STORAGE_PATH) .debug(i % 2 == 0) .build(); lifecycleManager.restart(config); @@ -165,7 +192,7 @@ void testMultipleRestarts() throws DataManagerException { void testStateTransitions() throws DataManagerException { assertEquals(LifecycleState.NOT_INITIALIZED, lifecycleManager.getState()); - DataManagerConfig config = DataManagerConfig.builder().build(); + DataManagerConfig config = createDefaultConfig(); lifecycleManager.initialize(config); assertEquals(LifecycleState.RUNNING, lifecycleManager.getState()); diff --git a/src/test/java/fr/inria/corese/core/storage/impl/operations/BulkOperationsImplTest.java b/src/test/java/fr/inria/corese/core/storage/impl/operations/BulkOperationsImplTest.java index 147dd2af9..80effdaa0 100644 --- a/src/test/java/fr/inria/corese/core/storage/impl/operations/BulkOperationsImplTest.java +++ b/src/test/java/fr/inria/corese/core/storage/impl/operations/BulkOperationsImplTest.java @@ -3,8 +3,8 @@ import fr.inria.corese.core.Graph; import fr.inria.corese.core.kgram.api.core.Edge; import fr.inria.corese.core.kgram.api.core.Node; -import fr.inria.corese.core.storage.api.dataManager.support.exception.DataManagerException; -import fr.inria.corese.core.storage.api.dataManager.support.model.MutationResult; +import fr.inria.corese.core.storage.api.datamanager.support.exception.DataManagerException; +import fr.inria.corese.core.storage.api.datamanager.support.model.MutationResult; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/src/test/java/fr/inria/corese/core/storage/impl/operations/MetadataOperationsImplTest.java b/src/test/java/fr/inria/corese/core/storage/impl/operations/MetadataOperationsImplTest.java index 2e440c40a..a300ac771 100644 --- a/src/test/java/fr/inria/corese/core/storage/impl/operations/MetadataOperationsImplTest.java +++ b/src/test/java/fr/inria/corese/core/storage/impl/operations/MetadataOperationsImplTest.java @@ -2,8 +2,8 @@ import fr.inria.corese.core.Graph; import fr.inria.corese.core.kgram.api.core.Node; -import fr.inria.corese.core.storage.api.dataManager.support.exception.DataManagerException; -import fr.inria.corese.core.storage.api.dataManager.support.model.GraphStatistics; +import fr.inria.corese.core.storage.api.datamanager.support.exception.DataManagerException; +import fr.inria.corese.core.storage.api.datamanager.support.model.GraphStatistics; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/src/test/java/fr/inria/corese/core/storage/impl/operations/MutationOperationsImplTest.java b/src/test/java/fr/inria/corese/core/storage/impl/operations/MutationOperationsImplTest.java index ad71abde6..ca70d236d 100644 --- a/src/test/java/fr/inria/corese/core/storage/impl/operations/MutationOperationsImplTest.java +++ b/src/test/java/fr/inria/corese/core/storage/impl/operations/MutationOperationsImplTest.java @@ -3,8 +3,8 @@ import fr.inria.corese.core.Graph; import fr.inria.corese.core.kgram.api.core.Edge; import fr.inria.corese.core.kgram.api.core.Node; -import fr.inria.corese.core.storage.api.dataManager.support.exception.DataManagerException; -import fr.inria.corese.core.storage.api.dataManager.support.model.MutationResult; +import fr.inria.corese.core.storage.api.datamanager.support.exception.DataManagerException; +import fr.inria.corese.core.storage.api.datamanager.support.model.MutationResult; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/src/test/java/fr/inria/corese/core/storage/impl/operations/QueryOperationsImplTest.java b/src/test/java/fr/inria/corese/core/storage/impl/operations/QueryOperationsImplTest.java index 65c210515..4f266ed16 100644 --- a/src/test/java/fr/inria/corese/core/storage/impl/operations/QueryOperationsImplTest.java +++ b/src/test/java/fr/inria/corese/core/storage/impl/operations/QueryOperationsImplTest.java @@ -3,8 +3,8 @@ import fr.inria.corese.core.Graph; import fr.inria.corese.core.kgram.api.core.Edge; import fr.inria.corese.core.kgram.api.core.Node; -import fr.inria.corese.core.storage.api.dataManager.support.exception.DataManagerException; -import fr.inria.corese.core.storage.api.dataManager.support.model.EdgePattern; +import fr.inria.corese.core.storage.api.datamanager.support.exception.DataManagerException; +import fr.inria.corese.core.storage.api.datamanager.support.model.EdgePattern; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; From c1e5458bbf2feb6659e057909857b4d1acbd771c Mon Sep 17 00:00:00 2001 From: "AD\\aabdoun" Date: Wed, 28 Jan 2026 14:13:57 +0100 Subject: [PATCH 8/8] Refactor DataManager architecture revue --- .../support/config/DataManagerConfig.java | 34 -- .../support/model/EdgePattern.java | 10 +- .../support/model/GraphStatistics.java | 351 +++++++++++++----- .../impl/lifecycle/LifecycleManagerImpl.java | 3 - .../operations/MetadataOperationsImpl.java | 15 +- .../impl/operations/QueryOperationsImpl.java | 4 +- ...CoreseGraphDataManagerIntegrationTest.java | 6 - .../lifecycle/LifecycleManagerImplTest.java | 10 - 8 files changed, 272 insertions(+), 161 deletions(-) diff --git a/src/main/java/fr/inria/corese/core/storage/api/datamanager/support/config/DataManagerConfig.java b/src/main/java/fr/inria/corese/core/storage/api/datamanager/support/config/DataManagerConfig.java index 45f3da571..3c340e3a8 100644 --- a/src/main/java/fr/inria/corese/core/storage/api/datamanager/support/config/DataManagerConfig.java +++ b/src/main/java/fr/inria/corese/core/storage/api/datamanager/support/config/DataManagerConfig.java @@ -11,7 +11,6 @@ public final class DataManagerConfig { private final Map properties; private final boolean transactionSupport; - private final String storagePath; private final boolean debug; /** @@ -20,7 +19,6 @@ public final class DataManagerConfig { private DataManagerConfig(Builder builder) { this.properties = Map.copyOf(builder.properties); this.transactionSupport = builder.transactionSupport; - this.storagePath = builder.storagePath; this.debug = builder.debug; } @@ -60,14 +58,6 @@ public Map getProperties() { return properties; } - /** - * Returns the storage path. - * - * @return Storage path (never null) - */ - public String getStoragePath() { - return storagePath; - } /** * Indicates whether debug mode is enabled. @@ -90,7 +80,6 @@ public static Builder builder() { @Override public String toString() { return "DataManagerConfig{" + - "storagePath='" + storagePath + '\'' + ", transactionSupport=" + transactionSupport + ", debug=" + debug + ", properties=" + properties + @@ -103,7 +92,6 @@ public String toString() { public static final class Builder { private final Map properties = new HashMap<>(); private boolean transactionSupport = false; - private String storagePath = null; private boolean debug = false; private Builder() { @@ -149,22 +137,6 @@ public Builder transactionSupport(boolean enable) { return enableTransactions(enable); } - /** - * Sets the storage path for the DataManager. - * This is a required configuration parameter. - * - * @param path Storage path (must not be null, empty, or blank) - * @return this builder instance - * @throws IllegalArgumentException if path is null, empty, or blank - */ - public Builder storagePath(String path) { - if (path == null || path.isBlank()) { - throw new IllegalArgumentException("Storage path cannot be null, empty, or blank"); - } - this.storagePath = path; - return this; - } - /** * Enables or disables debug mode. * @@ -183,12 +155,6 @@ public Builder debug(boolean debug) { * @throws IllegalStateException if storagePath has not been set */ public DataManagerConfig build() { - if (storagePath == null || storagePath.isBlank()) { - throw new IllegalStateException( - "Storage path must be set before building configuration. " + - "Use storagePath(String) to specify the storage location." - ); - } return new DataManagerConfig(this); } } diff --git a/src/main/java/fr/inria/corese/core/storage/api/datamanager/support/model/EdgePattern.java b/src/main/java/fr/inria/corese/core/storage/api/datamanager/support/model/EdgePattern.java index 4cb06a495..9f715d469 100644 --- a/src/main/java/fr/inria/corese/core/storage/api/datamanager/support/model/EdgePattern.java +++ b/src/main/java/fr/inria/corese/core/storage/api/datamanager/support/model/EdgePattern.java @@ -68,7 +68,7 @@ public List getContexts() { * * @return true if subject is specified */ - public boolean isSubject() { + public boolean hasSubject() { return subject == null; } @@ -77,7 +77,7 @@ public boolean isSubject() { * * @return true if predicate is specified */ - public boolean isPredicate() { + public boolean hasPredicate() { return predicate == null; } @@ -86,7 +86,7 @@ public boolean isPredicate() { * * @return true if object is specified */ - public boolean isObject() { + public boolean hasObject() { return object == null; } @@ -95,7 +95,7 @@ public boolean isObject() { * * @return true if contexts are specified */ - public boolean isContexts() { + public boolean hasContexts() { return contexts == null || contexts.isEmpty(); } @@ -105,7 +105,7 @@ public boolean isContexts() { * @return true if no constraints */ public boolean matchesAll() { - return isSubject() && isPredicate() && isObject() && isContexts(); + return hasSubject() && hasPredicate() && hasObject() && hasContexts(); } /** diff --git a/src/main/java/fr/inria/corese/core/storage/api/datamanager/support/model/GraphStatistics.java b/src/main/java/fr/inria/corese/core/storage/api/datamanager/support/model/GraphStatistics.java index 9d77c1ace..4bcb9a251 100644 --- a/src/main/java/fr/inria/corese/core/storage/api/datamanager/support/model/GraphStatistics.java +++ b/src/main/java/fr/inria/corese/core/storage/api/datamanager/support/model/GraphStatistics.java @@ -4,30 +4,55 @@ /** * Statistics about a graph's content and structure. + * This class is mutable to allow updates as the graph changes. */ public class GraphStatistics { - private final long edgeCount; - private final long nodeCount; - private final long predicateCount; - private final long contextCount; - private final long subjectCount; - private final long objectCount; - private final long literalCount; + private long edgeCount; + private long nodeCount; + private long predicateCount; + private long contextCount; + private long subjectCount; + private long objectCount; + private long literalCount; /** - * Private constructor - use Builder. + * Default constructor - initializes all counts to 0. */ - private GraphStatistics(Builder builder) { - this.edgeCount = builder.edgeCount; - this.nodeCount = builder.nodeCount; - this.predicateCount = builder.predicateCount; - this.contextCount = builder.contextCount; - this.subjectCount = builder.subjectCount; - this.objectCount = builder.objectCount; - this.literalCount = builder.literalCount; + public GraphStatistics() { + this.edgeCount = 0; + this.nodeCount = 0; + this.predicateCount = 0; + this.contextCount = 0; + this.subjectCount = 0; + this.objectCount = 0; + this.literalCount = 0; } + /** + * Constructor with initial values. + * + * @param edgeCount Initial edge count + * @param nodeCount Initial node count + * @param predicateCount Initial predicate count + * @param contextCount Initial context count + * @param subjectCount Initial subject count + * @param objectCount Initial object count + * @param literalCount Initial literal count + */ + public GraphStatistics(long edgeCount, long nodeCount, long predicateCount, + long contextCount, long subjectCount, long objectCount, + long literalCount) { + this.edgeCount = Math.max(0, edgeCount); + this.nodeCount = Math.max(0, nodeCount); + this.predicateCount = Math.max(0, predicateCount); + this.contextCount = Math.max(0, contextCount); + this.subjectCount = Math.max(0, subjectCount); + this.objectCount = Math.max(0, objectCount); + this.literalCount = Math.max(0, literalCount); + } + + /** * Returns the total number of edges (triples) in the graph. * @@ -64,6 +89,221 @@ public long getContextCount() { return contextCount; } + /** + * Returns the number of unique subjects. + * + * @return Subject count + */ + public long getSubjectCount() { + return subjectCount; + } + + /** + * Returns the number of unique objects. + * + * @return Object count + */ + public long getObjectCount() { + return objectCount; + } + + /** + * Returns the number of literals. + * + * @return Literal count + */ + public long getLiteralCount() { + return literalCount; + } + + /** + * Sets the edge count. + * + * @param count Edge count (negative values are set to 0) + */ + public void setEdgeCount(long count) { + this.edgeCount = Math.max(0, count); + } + + /** + * Sets the node count. + * + * @param count Node count (negative values are set to 0) + */ + public void setNodeCount(long count) { + this.nodeCount = Math.max(0, count); + } + + /** + * Sets the predicate count. + * + * @param count Predicate count (negative values are set to 0) + */ + public void setPredicateCount(long count) { + this.predicateCount = Math.max(0, count); + } + + /** + * Sets the context count. + * + * @param count Context count (negative values are set to 0) + */ + public void setContextCount(long count) { + this.contextCount = Math.max(0, count); + } + + /** + * Sets the subject count. + * + * @param count Subject count (negative values are set to 0) + */ + public void setSubjectCount(long count) { + this.subjectCount = Math.max(0, count); + } + + /** + * Sets the object count. + * + * @param count Object count (negative values are set to 0) + */ + public void setObjectCount(long count) { + this.objectCount = Math.max(0, count); + } + + /** + * Sets the literal count. + * + * @param count Literal count (negative values are set to 0) + */ + public void setLiteralCount(long count) { + this.literalCount = Math.max(0, count); + } + + /** + * Increments the edge count by 1. + */ + public void incrementEdgeCount() { + this.edgeCount++; + } + + /** + * Decrements the edge count by 1 (minimum 0). + */ + public void decrementEdgeCount() { + if (this.edgeCount > 0) { + this.edgeCount--; + } + } + + /** + * Increments the node count by 1. + */ + public void incrementNodeCount() { + this.nodeCount++; + } + + /** + * Decrements the node count by 1 (minimum 0). + */ + public void decrementNodeCount() { + if (this.nodeCount > 0) { + this.nodeCount--; + } + } + + /** + * Increments the predicate count by 1. + */ + public void incrementPredicateCount() { + this.predicateCount++; + } + + /** + * Decrements the predicate count by 1 (minimum 0). + */ + public void decrementPredicateCount() { + if (this.predicateCount > 0) { + this.predicateCount--; + } + } + + /** + * Increments the context count by 1. + */ + public void incrementContextCount() { + this.contextCount++; + } + + /** + * Decrements the context count by 1 (minimum 0). + */ + public void decrementContextCount() { + if (this.contextCount > 0) { + this.contextCount--; + } + } + + /** + * Increments the subject count by 1. + */ + public void incrementSubjectCount() { + this.subjectCount++; + } + + /** + * Decrements the subject count by 1 (minimum 0). + */ + public void decrementSubjectCount() { + if (this.subjectCount > 0) { + this.subjectCount--; + } + } + + /** + * Increments the object count by 1. + */ + public void incrementObjectCount() { + this.objectCount++; + } + + /** + * Decrements the object count by 1 (minimum 0). + */ + public void decrementObjectCount() { + if (this.objectCount > 0) { + this.objectCount--; + } + } + + /** + * Increments the literal count by 1. + */ + public void incrementLiteralCount() { + this.literalCount++; + } + + /** + * Decrements the literal count by 1 (minimum 0). + */ + public void decrementLiteralCount() { + if (this.literalCount > 0) { + this.literalCount--; + } + } + + /** + * Resets all statistics to 0. + */ + public void reset() { + this.edgeCount = 0; + this.nodeCount = 0; + this.predicateCount = 0; + this.contextCount = 0; + this.subjectCount = 0; + this.objectCount = 0; + this.literalCount = 0; + } + /** * Calculates graph density: edges / (nodes * predicates). @@ -78,7 +318,6 @@ public double getDensity() { return (double) edgeCount / maxPossibleEdges; } - /** * Calculates average degree (number of edges) per node. * @@ -109,7 +348,7 @@ public boolean isEmpty() { public boolean hasData() { return edgeCount > 0; } - + @Override public String toString() { return String.format( @@ -140,80 +379,4 @@ public int hashCode() { return Objects.hash(edgeCount, nodeCount, predicateCount, contextCount, subjectCount, objectCount, literalCount); } - - /** - * Creates a new builder. - * - * @return New builder instance - */ - public static Builder builder() { - return new Builder(); - } - - /** - * Builder for GraphStatistics. - */ - public static class Builder { - private long edgeCount = 0; - private long nodeCount = 0; - private long predicateCount = 0; - private long contextCount = 0; - private final long subjectCount = 0; - private final long objectCount = 0; - private final long literalCount = 0; - - /** - * Sets the edge count. - * - * @param count Edge count - * @return This builder - */ - public Builder edgeCount(long count) { - this.edgeCount = Math.max(0, count); - return this; - } - - /** - * Sets the node count. - * - * @param count Node count - * @return This builder - */ - public Builder nodeCount(long count) { - this.nodeCount = Math.max(0, count); - return this; - } - - /** - * Sets the predicate count. - * - * @param count Predicate count - * @return This builder - */ - public Builder predicateCount(long count) { - this.predicateCount = Math.max(0, count); - return this; - } - - /** - * Sets the context count. - * - * @param count Context count - * @return This builder - */ - public Builder contextCount(long count) { - this.contextCount = Math.max(0, count); - return this; - } - - - /** - * Builds the GraphStatistics instance. - * - * @return New GraphStatistics instance - */ - public GraphStatistics build() { - return new GraphStatistics(this); - } - } } \ No newline at end of file diff --git a/src/main/java/fr/inria/corese/core/storage/impl/lifecycle/LifecycleManagerImpl.java b/src/main/java/fr/inria/corese/core/storage/impl/lifecycle/LifecycleManagerImpl.java index ee9d3600f..aa88d5d8a 100644 --- a/src/main/java/fr/inria/corese/core/storage/impl/lifecycle/LifecycleManagerImpl.java +++ b/src/main/java/fr/inria/corese/core/storage/impl/lifecycle/LifecycleManagerImpl.java @@ -183,9 +183,6 @@ private void applyConfiguration(DataManagerConfig config) { logger.debug("Debug mode enabled"); } - // Log storage path information - logger.info("Storage path: {}", config.getStoragePath()); - // Other configurations can be added here config.getProperties().forEach((key, value) -> logger.debug("Config property: {} = {}", key, value)); } diff --git a/src/main/java/fr/inria/corese/core/storage/impl/operations/MetadataOperationsImpl.java b/src/main/java/fr/inria/corese/core/storage/impl/operations/MetadataOperationsImpl.java index 81261d27c..f66f7d237 100644 --- a/src/main/java/fr/inria/corese/core/storage/impl/operations/MetadataOperationsImpl.java +++ b/src/main/java/fr/inria/corese/core/storage/impl/operations/MetadataOperationsImpl.java @@ -154,13 +154,14 @@ public GraphStatistics getStatistics() throws DataManagerException { long predicateCount = getPredicates(null).size(); long contextCount = getContexts().size(); - // Build GraphStatistics using the builder pattern - GraphStatistics stats = GraphStatistics.builder() - .edgeCount(edgeCount) - .nodeCount(nodeCount) - .predicateCount(predicateCount) - .contextCount(contextCount) - .build(); + // Create GraphStatistics using constructor with parameters + GraphStatistics stats = new GraphStatistics( + edgeCount, + nodeCount, + predicateCount, + contextCount, + 0, 0,0 + ); logger.debug("Statistics collected: {}", stats); return stats; diff --git a/src/main/java/fr/inria/corese/core/storage/impl/operations/QueryOperationsImpl.java b/src/main/java/fr/inria/corese/core/storage/impl/operations/QueryOperationsImpl.java index 0c90e8127..740b96365 100644 --- a/src/main/java/fr/inria/corese/core/storage/impl/operations/QueryOperationsImpl.java +++ b/src/main/java/fr/inria/corese/core/storage/impl/operations/QueryOperationsImpl.java @@ -99,8 +99,8 @@ public long count(EdgePattern pattern) throws DataManagerException { logger.debug("Counting with pattern: {}", pattern); // Optimize: if only predicate is specified, use Graph.size(predicate) - if (!pattern.isPredicate() && pattern.isSubject() && - pattern.isObject() && pattern.isContexts()) { + if (!pattern.hasPredicate() && pattern.hasSubject() && + pattern.hasObject() && pattern.hasContexts()) { int count = graph.size(pattern.getPredicate()); logger.debug("Count (optimized by predicate): {}", count); diff --git a/src/test/java/fr/inria/corese/core/storage/CoreseGraphDataManagerIntegrationTest.java b/src/test/java/fr/inria/corese/core/storage/CoreseGraphDataManagerIntegrationTest.java index 8cf5cd82e..fc2e83ec6 100644 --- a/src/test/java/fr/inria/corese/core/storage/CoreseGraphDataManagerIntegrationTest.java +++ b/src/test/java/fr/inria/corese/core/storage/CoreseGraphDataManagerIntegrationTest.java @@ -2,7 +2,6 @@ import fr.inria.corese.core.kgram.api.core.Edge; import fr.inria.corese.core.kgram.api.core.Node; -import fr.inria.corese.core.storage.api.datamanager.support.config.DataManagerConfig; import fr.inria.corese.core.storage.api.datamanager.support.exception.DataManagerException; import fr.inria.corese.core.storage.api.datamanager.support.model.EdgePattern; import fr.inria.corese.core.storage.api.datamanager.support.model.GraphStatistics; @@ -30,11 +29,6 @@ class CoreseGraphDataManagerIntegrationTest { void setUp() { dataManager = new CoreseGraphDataManagerBuilder().build(); - // Initialize with transaction support enabled - DataManagerConfig config = DataManagerConfig.builder() - .storagePath("http://ns.inria.fr/corese/my-dataset") - .debug(true) - .build(); } @Test diff --git a/src/test/java/fr/inria/corese/core/storage/impl/lifecycle/LifecycleManagerImplTest.java b/src/test/java/fr/inria/corese/core/storage/impl/lifecycle/LifecycleManagerImplTest.java index 3972c7f56..8df049bf5 100644 --- a/src/test/java/fr/inria/corese/core/storage/impl/lifecycle/LifecycleManagerImplTest.java +++ b/src/test/java/fr/inria/corese/core/storage/impl/lifecycle/LifecycleManagerImplTest.java @@ -16,7 +16,6 @@ @DisplayName("LifecycleManagerImpl Tests") class LifecycleManagerImplTest { - private static final String TEST_STORAGE_PATH = "http://ns.inria.fr/corese/test-dataset"; private LifecycleManagerImpl lifecycleManager; @@ -31,7 +30,6 @@ void setUp() { */ private DataManagerConfig createDefaultConfig() { return DataManagerConfig.builder() - .storagePath(TEST_STORAGE_PATH) .build(); } @@ -40,7 +38,6 @@ private DataManagerConfig createDefaultConfig() { */ private DataManagerConfig createDebugConfig() { return DataManagerConfig.builder() - .storagePath(TEST_STORAGE_PATH) .debug(true) .build(); } @@ -85,7 +82,6 @@ void testShutdownSuccess() throws DataManagerException { @DisplayName("Restart should reinitialize successfully") void testRestartSuccess() throws DataManagerException { DataManagerConfig config1 = DataManagerConfig.builder() - .storagePath(TEST_STORAGE_PATH) .debug(false) .build(); lifecycleManager.initialize(config1); @@ -128,21 +124,18 @@ void testCheckUsableAfterShutdown() throws DataManagerException { void testInitializeWithCustomStoragePath() throws DataManagerException { String customPath = "http://example.org/custom/storage"; DataManagerConfig config = DataManagerConfig.builder() - .storagePath(customPath) .build(); lifecycleManager.initialize(config); assertTrue(lifecycleManager.isInitialized()); assertEquals(LifecycleState.RUNNING, lifecycleManager.getState()); - assertEquals(customPath, config.getStoragePath()); } @Test @DisplayName("Initialize with transaction support enabled") void testInitializeWithTransactionSupport() throws DataManagerException { DataManagerConfig config = DataManagerConfig.builder() - .storagePath(TEST_STORAGE_PATH) .transactionSupport(true) .build(); @@ -155,7 +148,6 @@ void testInitializeWithTransactionSupport() throws DataManagerException { @DisplayName("Initialize with custom properties") void testInitializeWithCustomProperties() throws DataManagerException { DataManagerConfig config = DataManagerConfig.builder() - .storagePath(TEST_STORAGE_PATH) .property("custom.key", "custom.value") .property("another.key", "another.value") .build(); @@ -170,14 +162,12 @@ void testInitializeWithCustomProperties() throws DataManagerException { @DisplayName("Multiple restarts should work correctly") void testMultipleRestarts() throws DataManagerException { DataManagerConfig config1 = DataManagerConfig.builder() - .storagePath(TEST_STORAGE_PATH) .debug(false) .build(); lifecycleManager.initialize(config1); for (int i = 0; i < 3; i++) { DataManagerConfig config = DataManagerConfig.builder() - .storagePath(TEST_STORAGE_PATH) .debug(i % 2 == 0) .build(); lifecycleManager.restart(config);