diff --git a/CHANGELOG.md b/CHANGELOG.md index 4967c8f9f..558645e2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ - Lyo Validation now uses Jena's native support for SHACL instead of relying on ShaclEx - InputStream is now the preferred interface for initializing OslcQueryResult - RootServicesHelper can be initialized using an InputStream +- TRS Client uses Lyo Store instead of using Eclipse RDF4J directly. - `Error` and `ExtendedError` classes now extend `AbstractResource`, implementing `IExtendedResource`. This allows setting extended properties like `dcterms:description` on OSLC error responses. - InMemPagedTRS handles concurrency. - When unmarshaling the RDF model, if OSLC4J_USE_BEAN_CLASS_FOR_PARSING is true, introduce a fallback mechanism to try to identify the rdf:type, if no match with the OSLC annotations is found. @@ -47,25 +48,14 @@ This release does not contain security updates. ### Added -- Introducing capability to set the `servletUri` to be used by the `OAuthConfiguration` -- OSLC PROMCODE domain model and generated POJOs -- Support for additional request headers to `OslcQuery` +- Introducing capability to set the servletUri to be used by the OAuthConfiguration +- `Store.rawUpdateQuery(String)` allows making raw SPARQL UPDATE queries. ### Changed -- 🧨 Migrated from Java EE (`javax.` namespace) to Jakarta packages -- Upgrade to Jersey 3.1.5 -- **JDK 17 is the new baseline for Eclipse Lyo.** The SDK and sample code has - been tested using JDK 17, 21, 23, and 24-ea. -- Kotlin 1.9.0 is used; `kotlin-stdlib-jdk8` dependency was replaced with - `kotlin-stdlib` due to - [Kotlin updates](https://kotlinlang.org/docs/whatsnew18.html#updated-jvm-compilation-target). -- Allow application to reset the OAuth token cached within the server, when it - deems that it is no longer valid -- 🧨 Corrected cardinality and range of the `oslc_config:acceptedBy` property (from - String[0..1] to Resource[0..*]) -- Changed scope of dependencies in `oauth-webapp` to avoid inclusion multiple times - during runtime. +- Kotlin 1.9.0 is used; `kotlin-stdlib-jdk8` dependency was replaced with `kotlin-stdlib` due to [Kotlin updates](https://kotlinlang.org/docs/whatsnew18.html#updated-jvm-compilation-target). +- Allow application to reset the oauth token cached within the server, when it deems that it is no longer valid +- 🧨Corrected cardinality and range of the oslc_config:acceptedBy property (from String[0..1] to Resource[0..*]) ### Deprecated @@ -73,8 +63,8 @@ This release does not introduce deprecations. ### Removed -- 🧨 Support for JDK 11 (and all versions below 17) is removed. -- 🧨 Support for Java EE and Jakarta EE 8 is removed. +- 🧨 Support for JDK 11 (and all versions below 17) is removed. **JDK 17 is the new baseline for Eclipse Lyo.** The SDK and sample code has been tested using JDK 17, 20, and 21-ea. +- TRS Client no longer depends on Eclipse RDF4J. Helper methods for RDF4J were also removed. ### Fixed diff --git a/store/store-core/src/main/java/org/eclipse/lyo/store/Store.java b/store/store-core/src/main/java/org/eclipse/lyo/store/Store.java index b3b0d37a5..a1e3e70c4 100644 --- a/store/store-core/src/main/java/org/eclipse/lyo/store/Store.java +++ b/store/store-core/src/main/java/org/eclipse/lyo/store/Store.java @@ -1,14 +1,3 @@ -package org.eclipse.lyo.store; - -import java.net.URI; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.NoSuchElementException; -import java.util.Set; - -import org.apache.jena.arq.querybuilder.SelectBuilder; - /* * Copyright (c) 2020 Contributors to the Eclipse Foundation * @@ -22,7 +11,15 @@ * * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause */ +package org.eclipse.lyo.store; +import java.net.URI; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Set; +import org.apache.jena.arq.querybuilder.SelectBuilder; import org.apache.jena.rdf.model.Model; import org.eclipse.lyo.oslc4j.core.model.IResource; @@ -106,8 +103,7 @@ boolean insertResources(URI namedGraphUri, final Object... resources) /** * Retrieve a Jena {@link Model} for triples under the given subject from the corresponding named graph. */ - Model getJenaModelForSubject(URI namedGraphUri, URI subject) - throws NoSuchElementException; + Model getJenaModelForSubject(URI namedGraphUri, URI subject) throws NoSuchElementException; /** * Retrieve the collection of {@link IResource} instances specified by the concrete @@ -147,8 +143,9 @@ List getResources(URI namedGraphUri, final Class cla * @throws ModelUnmarshallingException if the classes cannot be instantiated or another error * occurred when working with Jena model. */ - List getResources(URI namedGraphUri, Class clazz, int limit, - int offset) throws StoreAccessException, ModelUnmarshallingException; + List getResources( + URI namedGraphUri, Class clazz, int limit, int offset) + throws StoreAccessException, ModelUnmarshallingException; /** * Alternative to {@link Store#getResources(URI, Class)} with paging on the OSLC resource level. @@ -168,9 +165,15 @@ List getResources(URI namedGraphUri, Class clazz, in * @throws ModelUnmarshallingException if the classes cannot be instantiated or another error * occurred when working with Jena model. */ - List getResources(URI namedGraphUri, Class clazz, - String prefixes, String where, String searchTerms, - int limit, int offset) throws StoreAccessException, ModelUnmarshallingException; + List getResources( + URI namedGraphUri, + Class clazz, + String prefixes, + String where, + String searchTerms, + int limit, + int offset) + throws StoreAccessException, ModelUnmarshallingException; /** * Alternative to {@link Store#getResources(URI, Class, String, String, String, int, int)} with additional parameters for inlined resources. @@ -219,10 +222,17 @@ List getResources(URI namedGraphUri, Class clazz, * @param additionalDistinctVars * @param additionalQueryFilter */ - List getResources(URI namedGraphUri, Class clazz, - String prefixes, String where, String searchTerms, - int limit, int offset, - List additionalDistinctVars, SelectBuilder additionalQueryFilter) throws StoreAccessException, ModelUnmarshallingException; + List getResources( + URI namedGraphUri, + Class clazz, + String prefixes, + String where, + String searchTerms, + int limit, + int offset, + List additionalDistinctVars, + SelectBuilder additionalQueryFilter) + throws StoreAccessException, ModelUnmarshallingException; /** * Retrieve a Jena model that satisfies the given where parameter as defined in the OSLC Query @@ -268,8 +278,13 @@ List getResources(URI namedGraphUri, Class clazz, * @return a Jena {@link Model} with the less than or equal to {@code limit} resources. * */ - Model getResources(URI namedGraph, String prefixes, String where, String searchTerms, int limit, int offset); - + Model getResources( + URI namedGraph, + String prefixes, + String where, + String searchTerms, + int limit, + int offset); /** * Alternative to {@link Store#getResources(URI, String, String, String, int, int)} with @@ -277,10 +292,17 @@ List getResources(URI namedGraphUri, Class clazz, * * See {@link Store#getResources(URI, Class, String, String, String, int, int, List, SelectBuilder)} * for an explanation of these additional parameters. - + * */ - Model getResources(URI namedGraph, String prefixes, String where, String searchTerms, int limit, int offset, - List additionalDistinctVars, SelectBuilder additionalQueryFilter); + Model getResources( + URI namedGraph, + String prefixes, + String where, + String searchTerms, + int limit, + int offset, + List additionalDistinctVars, + SelectBuilder additionalQueryFilter); /** * Retrieve a single {@link IResource} instance specified by the concrete @@ -398,4 +420,11 @@ default boolean appendResource(URI namedGraphUri, final T * @since 4.1.0 */ void close(); + + /** + * Execute a raw SPARQL query against the UPDATE endpoint. + * + * @param finalQueryString + */ + void rawUpdateQuery(String finalQueryString) throws StoreAccessException; } diff --git a/store/store-core/src/main/java/org/eclipse/lyo/store/internals/SparqlStoreImpl.java b/store/store-core/src/main/java/org/eclipse/lyo/store/internals/SparqlStoreImpl.java index c191f4af1..0fcac592e 100644 --- a/store/store-core/src/main/java/org/eclipse/lyo/store/internals/SparqlStoreImpl.java +++ b/store/store-core/src/main/java/org/eclipse/lyo/store/internals/SparqlStoreImpl.java @@ -1,3 +1,16 @@ +/* + * Copyright (c) 2020 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License 1.0 + * which is available at http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ package org.eclipse.lyo.store.internals; import java.lang.reflect.Array; @@ -16,23 +29,7 @@ import java.util.NoSuchElementException; import java.util.Set; import java.util.stream.Collectors; - import javax.xml.datatype.DatatypeConfigurationException; - -/* - * Copyright (c) 2020 Contributors to the Eclipse Foundation - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License 1.0 - * which is available at http://www.eclipse.org/org/documents/edl-v10.php. - * - * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause - */ - import org.apache.jena.arq.querybuilder.DescribeBuilder; import org.apache.jena.arq.querybuilder.ExprFactory; import org.apache.jena.arq.querybuilder.Order; @@ -95,6 +92,7 @@ public class SparqlStoreImpl implements Store { * Could be used to prevent extremely large results */ public static final int TRIPLE_LIMIT = 10001; + private static final Logger log = LoggerFactory.getLogger(SparqlStoreImpl.class); private final JenaQueryExecutor queryExecutor; @@ -122,10 +120,14 @@ public SparqlStoreImpl(final String queryEndpoint, final String updateEndpoint) * @param username Username * @param password Password */ - public SparqlStoreImpl(final String queryEndpoint, final String updateEndpoint, - final String username, final String password) { - this(new SparqlQueryExecutorBasicAuthImpl(queryEndpoint, updateEndpoint, username, - password)); + public SparqlStoreImpl( + final String queryEndpoint, + final String updateEndpoint, + final String username, + final String password) { + this( + new SparqlQueryExecutorBasicAuthImpl( + queryEndpoint, updateEndpoint, username, password)); } /** @@ -165,13 +167,15 @@ public boolean insertResources(final URI namedGraph, final Object... resources) final Model model = JenaModelHelper.createJenaModel(resources); insertJenaModel(namedGraph, model); return true; - } catch (DatatypeConfigurationException | IllegalAccessException | - OslcCoreApplicationException | InvocationTargetException e) { + } catch (DatatypeConfigurationException + | IllegalAccessException + | OslcCoreApplicationException + | InvocationTargetException e) { throw new StoreAccessException(e); } } - //this is highly inefficient. I need to replace with a single query that removes all nodes. + // this is highly inefficient. I need to replace with a single query that removes all nodes. @Override public void deleteResources(final URI namedGraphUri, final URI... subjectUris) { final QuerySolutionMap map = new QuerySolutionMap(); @@ -179,9 +183,11 @@ public void deleteResources(final URI namedGraphUri, final URI... subjectUris) { map.clear(); map.add("graph", new ResourceImpl(String.valueOf(namedGraphUri))); map.add("subject", new ResourceImpl(String.valueOf(uris))); - final ParameterizedSparqlString sparqlString = new ParameterizedSparqlString( - "WITH ?graph DELETE { ?s ?p ?v } WHERE {?s ?p ?v . FILTER(?s = ?subject)}", - map); + final ParameterizedSparqlString sparqlString = + new ParameterizedSparqlString( + "WITH ?graph DELETE { ?s ?p ?v } WHERE {?s ?p ?v . FILTER(?s =" + + " ?subject)}", + map); final String query = sparqlString.toString(); final UpdateProcessor updateProcessor = queryExecutor.prepareSparqlUpdate(query); updateProcessor.execute(); @@ -201,13 +207,15 @@ public void deleteResources(final URI namedGraphUri, final IResource... resource public boolean namedGraphExists(final URI namedGraphUri) { final QuerySolutionMap map = new QuerySolutionMap(); map.add("g", new ResourceImpl(String.valueOf(namedGraphUri))); - final ParameterizedSparqlString sparqlString = new ParameterizedSparqlString( - "ASK {GRAPH ?g {?s ?p ?o} }", map); - final String query = sparqlString.toString(); + + // Check if graph exists by looking for any triples in the graph + final ParameterizedSparqlString sparqlString = + new ParameterizedSparqlString("ASK WHERE { GRAPH ?g { ?s ?p ?o } }", map); queryExecutor.beginRead(); try { - final QueryExecution queryExecution = queryExecutor.prepareSparqlQuery(query); + final QueryExecution queryExecution = + queryExecutor.prepareSparqlQuery(sparqlString.toString()); return queryExecution.execAsk(); } finally { queryExecutor.end(); @@ -219,8 +227,8 @@ public boolean resourceExists(final URI namedGraphUri, final URI resourceUri) { final QuerySolutionMap map = new QuerySolutionMap(); map.add("g", new ResourceImpl(String.valueOf(namedGraphUri))); map.add("s", new ResourceImpl(String.valueOf(resourceUri))); - final ParameterizedSparqlString sparqlString = new ParameterizedSparqlString( - "ASK {GRAPH ?g {?s ?p ?o} }", map); + final ParameterizedSparqlString sparqlString = + new ParameterizedSparqlString("ASK {GRAPH ?g {?s ?p ?o} }", map); final String query = sparqlString.toString(); queryExecutor.beginRead(); @@ -236,103 +244,153 @@ public boolean resourceExists(final URI namedGraphUri, final URI resourceUri) { public Model getJenaModelForSubject(final URI namedGraphUri, final URI subject) throws NoSuchElementException { if (!namedGraphExists(namedGraphUri)) { - throw new NoSuchElementException("namedGraph '" + namedGraphUri + "' is missing from the triplestore"); + throw new NoSuchElementException( + "namedGraph '" + namedGraphUri + "' is missing from the triplestore"); } final Model model; model = modelFromQueryByUri(namedGraphUri, subject); if (model.isEmpty()) { - throw new NoSuchElementException("resource '" + subject + "' is missing from the triplestore at namedGraph '" - + namedGraphUri + "'"); + throw new NoSuchElementException( + "resource '" + + subject + + "' is missing from the triplestore at namedGraph '" + + namedGraphUri + + "'"); } return model; } @Override - public List getResources(final URI namedGraph, - final Class clazz) throws StoreAccessException, ModelUnmarshallingException { + public List getResources(final URI namedGraph, final Class clazz) + throws StoreAccessException, ModelUnmarshallingException { if (namedGraphExists(namedGraph)) { final Model model; model = modelFromQueryFlat(namedGraph); return getResourcesFromModel(model, clazz); } else { - throw new IllegalArgumentException("Named graph" - + namedGraph - + " was missing from " - + "the triplestore"); + throw new IllegalArgumentException( + "Named graph" + namedGraph + " was missing from " + "the triplestore"); } } @Override - public List getResources(final URI namedGraph, - final Class clazz, final int limit, final int offset) + public List getResources( + final URI namedGraph, final Class clazz, final int limit, final int offset) throws StoreAccessException, ModelUnmarshallingException { if (namedGraphExists(namedGraph)) { final Model model; model = modelFromQueryFlatPaged(namedGraph, getResourceNsUri(clazz), limit, offset); return getResourcesFromModel(model, clazz); } else { - throw new IllegalArgumentException("Named graph" - + namedGraph - + " was missing from " - + "the triplestore"); + throw new IllegalArgumentException( + "Named graph" + namedGraph + " was missing from " + "the triplestore"); } } @Override - public List getResources(final URI namedGraph, final Class clazz, final String prefixes, - final String where, final String searchTerms, final int limit, final int offset) + public List getResources( + final URI namedGraph, + final Class clazz, + final String prefixes, + final String where, + final String searchTerms, + final int limit, + final int offset) throws StoreAccessException, ModelUnmarshallingException { - return getResources(namedGraph, clazz, prefixes, where, searchTerms, limit, offset, - null, null); + return getResources( + namedGraph, clazz, prefixes, where, searchTerms, limit, offset, null, null); } @Override - public List getResources(final URI namedGraph, final Class clazz, final String prefixes, - final String where, final String searchTerms, final int limit, final int offset, - List additionalDistinctVars, SelectBuilder additionalQueryFilter) - throws StoreAccessException, ModelUnmarshallingException { + public List getResources( + final URI namedGraph, + final Class clazz, + final String prefixes, + final String where, + final String searchTerms, + final int limit, + final int offset, + List additionalDistinctVars, + SelectBuilder additionalQueryFilter) + throws StoreAccessException, ModelUnmarshallingException { String _prefixes = prefixes; String _where = where; - _prefixes = (StringUtils.isNullOrEmpty(_prefixes) ? "" : _prefixes + ",") + oslcQueryPrefixes(clazz); - _where = (StringUtils.isNullOrEmpty(_where) ? "" : _where + " and ") + oslcQueryWhere(clazz); - Model model = getResources(namedGraph, _prefixes, _where, searchTerms, limit, offset, additionalDistinctVars, - additionalQueryFilter); + _prefixes = + (StringUtils.isNullOrEmpty(_prefixes) ? "" : _prefixes + ",") + + oslcQueryPrefixes(clazz); + _where = + (StringUtils.isNullOrEmpty(_where) ? "" : _where + " and ") + oslcQueryWhere(clazz); + Model model = + getResources( + namedGraph, + _prefixes, + _where, + searchTerms, + limit, + offset, + additionalDistinctVars, + additionalQueryFilter); return getResourcesFromModel(model, clazz); } @Override - public Model getResources(final URI namedGraph, final String prefixes, final String where, final int limit, - final int offset) { + public Model getResources( + final URI namedGraph, + final String prefixes, + final String where, + final int limit, + final int offset) { return getResources(namedGraph, prefixes, where, null, limit, offset); } @Override - public Model getResources(final URI namedGraph, final String prefixes, final String where, final String searchTerms, - final int limit, final int offset) { - return getResources(namedGraph, prefixes, where, searchTerms, limit, offset, - null, null); + public Model getResources( + final URI namedGraph, + final String prefixes, + final String where, + final String searchTerms, + final int limit, + final int offset) { + return getResources(namedGraph, prefixes, where, searchTerms, limit, offset, null, null); } @Override - public Model getResources(final URI namedGraph, final String prefixes, final String where, final String searchTerms, - final int limit, final int offset, List additionalDistinctVars, - SelectBuilder additionalQueryFilter) { + public Model getResources( + final URI namedGraph, + final String prefixes, + final String where, + final String searchTerms, + final int limit, + final int offset, + List additionalDistinctVars, + SelectBuilder additionalQueryFilter) { if (namedGraph != null) { - //Make sure the designated namedGraph exists, if it is specified. - //Otherwise, the search occurs across all named graphs. + // Make sure the designated namedGraph exists, if it is specified. + // Otherwise, the search occurs across all named graphs. if (!namedGraphExists(namedGraph)) { - throw new IllegalArgumentException("Named graph" + namedGraph + " was missing from the triplestore"); + throw new IllegalArgumentException( + "Named graph" + namedGraph + " was missing from the triplestore"); } } - SelectBuilder sparqlWhereQuery = constructSparqlWhere (prefixes, where, searchTerms, limit, offset, - additionalDistinctVars, additionalQueryFilter); + SelectBuilder sparqlWhereQuery = + constructSparqlWhere( + prefixes, + where, + searchTerms, + limit, + offset, + additionalDistinctVars, + additionalQueryFilter); DescribeBuilder describeBuilder = new DescribeBuilder(); - describeBuilder.addVar("s") - .addGraph((namedGraph != null) ? new ResourceImpl(String.valueOf(namedGraph)) : "?g", sparqlWhereQuery); + describeBuilder + .addVar("s") + .addGraph( + (namedGraph != null) ? new ResourceImpl(String.valueOf(namedGraph)) : "?g", + sparqlWhereQuery); if (null != additionalDistinctVars) { for (String additionalDistinctVar : additionalDistinctVars) { @@ -340,22 +398,30 @@ public Model getResources(final URI namedGraph, final String prefixes, final Str } } - Query describeQuery = describeBuilder.build() ; + Query describeQuery = describeBuilder.build(); String describeQueryString = describeQuery.toString(); Model execDescribe; queryExecutor.beginRead(); try { - final QueryExecution queryExecution = queryExecutor.prepareSparqlQuery(describeQueryString); + final QueryExecution queryExecution = + queryExecutor.prepareSparqlQuery(describeQueryString); try { - log.trace("SPARQL Describe query for oslc.where='{}':\n{}", where, describeQueryString); + log.trace( + "SPARQL Describe query for oslc.where='{}':\n{}", + where, + describeQueryString); Instant start = Instant.now(); execDescribe = queryExecution.execDescribe(); Instant finish = Instant.now(); - log.trace("GetResources - SPARQL Query Execution Duration: {} ms", Duration.between(start, finish).toMillis()); + log.trace( + "GetResources - SPARQL Query Execution Duration: {} ms", + Duration.between(start, finish).toMillis()); } catch (RiotException e) { - //a request that returns an empty set seems to cause an exception when using Marklogic. - if ((e.getCause() == null) && (e.getMessage().equals("[line: 2, col: 2 ] Out of place: [DOT]"))) { + // a request that returns an empty set seems to cause an exception when using + // Marklogic. + if ((e.getCause() == null) + && (e.getMessage().equals("[line: 2, col: 2 ] Out of place: [DOT]"))) { return ModelFactory.createDefaultModel(); } // Otherwise, there is a proper exception that we need to deal with! @@ -368,31 +434,33 @@ public Model getResources(final URI namedGraph, final String prefixes, final Str } @Override - public T getResource(final URI namedGraphUri, - final URI resourceUri, final Class clazz) + public T getResource( + final URI namedGraphUri, final URI resourceUri, final Class clazz) throws NoSuchElementException, StoreAccessException, ModelUnmarshallingException { final Model model = getJenaModelForSubject(namedGraphUri, resourceUri); final List modelResources = getResourcesFromModel(model, clazz); if (modelResources == null || modelResources.isEmpty()) { throw new NoSuchElementException( - "Empty Jena model for the subject " + resourceUri + ". Use resourceExists(g," + - "r) method to check for resource existence before calling this method" + - "."); + "Empty Jena model for the subject " + + resourceUri + + ". Use resourceExists(g," + + "r) method to check for resource existence before calling this method" + + "."); } return modelResources.get(0); } @Override - public boolean updateResources(final URI namedGraphUri, - final T... resources) throws StoreAccessException { - //No need to check if the resource exists. just delete it - if it is there. + public boolean updateResources( + final URI namedGraphUri, final T... resources) throws StoreAccessException { + // No need to check if the resource exists. just delete it - if it is there. deleteResources(namedGraphUri, resources); return insertResources(namedGraphUri, resources); } @Override - public boolean putResources(final URI uri, - final Collection resources) throws StoreAccessException { + public boolean putResources(final URI uri, final Collection resources) + throws StoreAccessException { if (namedGraphExists(uri)) { clear(uri); } @@ -400,16 +468,16 @@ public boolean putResources(final URI uri, } @Override - public boolean appendResources(final URI namedGraph, - final Collection resources) throws StoreAccessException { + public boolean appendResources( + final URI namedGraph, final Collection resources) throws StoreAccessException { return insertResources(namedGraph, resources.toArray()); } @Override public void clear(final URI namedGraph) { final QuerySolutionMap map = getGraphMap(namedGraph); - final ParameterizedSparqlString query = new ParameterizedSparqlString("CLEAR GRAPH ?g", - map); + final ParameterizedSparqlString query = + new ParameterizedSparqlString("CLEAR GRAPH ?g", map); queryExecutor.beginWrite(); try { final UpdateProcessor up = queryExecutor.prepareSparqlUpdate(query.toString()); @@ -437,6 +505,22 @@ public void close() { log.debug("Underlying SPARQL connection has been released"); } + @Override + public void rawUpdateQuery(String finalQueryString) throws StoreAccessException { + queryExecutor.beginWrite(); + try { + final UpdateProcessor updateProcessor = + queryExecutor.prepareSparqlUpdate(finalQueryString); + updateProcessor.execute(); + queryExecutor.commit(); + } catch (Exception e) { + // Don't commit on error - just end the transaction which will abort it + throw new StoreAccessException("Failed to execute raw update query", e); + } finally { + queryExecutor.end(); + } + } + private String oslcQueryPrefixes(final Class clazz) { return "rdf=" + "<" + org.apache.jena.vocabulary.RDF.uri + ">"; } @@ -445,25 +529,34 @@ private String oslcQueryWhere(final Class clazz) { return "rdf:type=" + "<" + getResourceNsUri(clazz) + ">"; } - private List getResourcesFromModel(final Model model, - final Class clazz) throws ModelUnmarshallingException, StoreAccessException { + private List getResourcesFromModel( + final Model model, final Class clazz) + throws ModelUnmarshallingException, StoreAccessException { try { Instant start = Instant.now(); final Object[] obj = JenaModelHelper.fromJenaModel(model, clazz); - @SuppressWarnings("unchecked") final T[] castObjects = (T[]) Array.newInstance(clazz, - obj.length); + @SuppressWarnings("unchecked") + final T[] castObjects = (T[]) Array.newInstance(clazz, obj.length); for (int i = 0; i < obj.length; i++) { castObjects[i] = clazz.cast(obj[i]); } Instant finish = Instant.now(); - log.trace("getResourcesFromModel - Execution Duration: {} ms", Duration.between(start, finish).toMillis()); - //The Model is most likely obtained via Select query that is orded by the subject (ascending) - //See sparql construction in constructSparqlWhere() - //Order the list below accordingly. + log.trace( + "getResourcesFromModel - Execution Duration: {} ms", + Duration.between(start, finish).toMillis()); + // The Model is most likely obtained via Select query that is orded by the subject + // (ascending) + // See sparql construction in constructSparqlWhere() + // Order the list below accordingly. return Arrays.stream(castObjects) - .sorted(Comparator.comparing(IResource::getAbout)).collect(Collectors.toList()); - } catch (InvocationTargetException | OslcCoreApplicationException | NoSuchMethodException - | URISyntaxException | DatatypeConfigurationException | InstantiationException e) { + .sorted(Comparator.comparing(IResource::getAbout)) + .collect(Collectors.toList()); + } catch (InvocationTargetException + | OslcCoreApplicationException + | NoSuchMethodException + | URISyntaxException + | DatatypeConfigurationException + | InstantiationException e) { throw new ModelUnmarshallingException(e); } catch (final IllegalAccessException e) { throw new StoreAccessException(e); @@ -492,14 +585,13 @@ private QuerySolutionMap getGraphMap(final URI namedGraph) { private Model modelFromQueryFlat(final URI namedGraph) { // TODO avoid CONSTRUCT query final QuerySolutionMap map = getGraphMap(namedGraph); - final String queryTemplate = "DESCRIBE ?s WHERE { GRAPH ?g { ?s " - + "?p " - + "?o } }"; + final String queryTemplate = "DESCRIBE ?s WHERE { GRAPH ?g { ?s " + "?p " + "?o } }"; final ParameterizedSparqlString query = new ParameterizedSparqlString(queryTemplate, map); queryExecutor.beginRead(); try { - final QueryExecution queryExecution = queryExecutor.prepareSparqlQuery(query.toString()); + final QueryExecution queryExecution = + queryExecutor.prepareSparqlQuery(query.toString()); return queryExecution.execDescribe(); } finally { queryExecutor.end(); @@ -522,10 +614,14 @@ private Model modelFromQueryByUri(final URI namedGraph, final URI uri) { Instant start = Instant.now(); execDescribe = queryExecution.execDescribe(); Instant finish = Instant.now(); - log.trace("GetResource - SPARQL Query Execution Duration: {} ms", Duration.between(start, finish).toMillis()); + log.trace( + "GetResource - SPARQL Query Execution Duration: {} ms", + Duration.between(start, finish).toMillis()); } catch (RiotException e) { - //a request that returns an empty set seems to cause an exception when using Marklogic. - if ((e.getCause() == null) && (e.getMessage().equals("[line: 2, col: 2 ] Out of place: [DOT]"))) { + // a request that returns an empty set seems to cause an exception when using + // Marklogic. + if ((e.getCause() == null) + && (e.getMessage().equals("[line: 2, col: 2 ] Out of place: [DOT]"))) { return ModelFactory.createDefaultModel(); } // Otherwise, there is a proper exception that we need to deal with! @@ -535,36 +631,36 @@ private Model modelFromQueryByUri(final URI namedGraph, final URI uri) { queryExecutor.end(); } return execDescribe; - } - private Model modelFromQueryFlatPaged(final URI namedGraph, final URI type, final int limit, - final int offset) { + private Model modelFromQueryFlatPaged( + final URI namedGraph, final URI type, final int limit, final int offset) { // TODO avoid CONSTRUCT query final Model m = ModelFactory.createDefaultModel(); final Resource typeResource = m.createResource(type.toString()); final QuerySolutionMap map = getGraphMap(namedGraph); map.add("t", typeResource); - final String queryTemplate = "PREFIX rdf: \n" - + "DESCRIBE ?s\n" - + "WHERE {\n" - + " GRAPH ?g {\n" - + " ?s ?p ?o\n" - + " {\n" - + " SELECT DISTINCT ?s\n" - + " WHERE {\n" - + " ?s ?p ?o .\n" - + " ?s rdf:type ?t.\n" - + " }\n" - + " ORDER BY ASC(?s)\n" - + " LIMIT ?l\n" - + " OFFSET " - + "?f\n" - + "}\n" - + "}\n" - + "}\n"; + final String queryTemplate = + "PREFIX rdf: \n" + + "DESCRIBE ?s\n" + + "WHERE {\n" + + " GRAPH ?g {\n" + + " ?s ?p ?o\n" + + " {\n" + + " SELECT DISTINCT ?s\n" + + " WHERE {\n" + + " ?s ?p ?o .\n" + + " ?s rdf:type ?t.\n" + + " }\n" + + " ORDER BY ASC(?s)\n" + + " LIMIT ?l\n" + + " OFFSET " + + "?f\n" + + "}\n" + + "}\n" + + "}\n"; // TODO: 15.02.17 add global triple limit just in case // + "\n" + "LIMIT " + TRIPLE_LIMIT + "\n" + "\n" + "\n"; @@ -575,7 +671,8 @@ private Model modelFromQueryFlatPaged(final URI namedGraph, final URI type, fina queryExecutor.beginRead(); try { - final QueryExecution queryExecution = queryExecutor.prepareSparqlQuery(query.toString()); + final QueryExecution queryExecution = + queryExecutor.prepareSparqlQuery(query.toString()); return queryExecution.execDescribe(); } finally { queryExecutor.end(); @@ -586,13 +683,18 @@ private Model modelFromQueryFlatPaged(final URI namedGraph, final URI type, fina * This method currently only provides support for terms of type {@link Type#COMPARISON}, where the operator is * 'EQUALS', and the operand is either a {@link String} or a {@link URI}. */ - private SelectBuilder constructSparqlWhere(final String prefixes, final String where, final String searchTerms, - final int limit, final int offset, List additionalDistinctVars, - SelectBuilder additionalQueryFilter) { + private SelectBuilder constructSparqlWhere( + final String prefixes, + final String where, + final String searchTerms, + final int limit, + final int offset, + List additionalDistinctVars, + SelectBuilder additionalQueryFilter) { SelectBuilder distinctResourcesQuery = new SelectBuilder(); - //Setup prefixes + // Setup prefixes Map prefixesMap = new HashMap<>(); try { if (!StringUtils.isNullOrEmpty(prefixes)) { @@ -605,10 +707,7 @@ private SelectBuilder constructSparqlWhere(final String prefixes, final String w throw new IllegalArgumentException("prefixesExpression could not be parsed", e); } - distinctResourcesQuery - .addVar( "s" ) - .setDistinct(true) - .addWhere( "?s", "?p", "?o"); + distinctResourcesQuery.addVar("s").setDistinct(true).addWhere("?s", "?p", "?o"); if (null != additionalDistinctVars) { for (String additionalDistinctVar : additionalDistinctVars) { @@ -619,7 +718,7 @@ private SelectBuilder constructSparqlWhere(final String prefixes, final String w distinctResourcesQuery.addWhere(additionalQueryFilter); } - //Setup where + // Setup where WhereClause whereClause = null; try { if (!StringUtils.isNullOrEmpty(where)) { @@ -630,12 +729,14 @@ private SelectBuilder constructSparqlWhere(final String prefixes, final String w PName property = simpleTerm.property(); if (!termType.equals(Type.COMPARISON)) { - throw new UnsupportedOperationException("only support for terms of type Comparisons"); + throw new UnsupportedOperationException( + "only support for terms of type Comparisons"); } ComparisonTerm aComparisonTerm = (ComparisonTerm) simpleTerm; if (!aComparisonTerm.operator().equals(Operator.EQUALS)) { throw new UnsupportedOperationException( - "only support for terms of type Comparisons, where the operator is 'EQUALS'"); + "only support for terms of type Comparisons, where the operator is" + + " 'EQUALS'"); } Value comparisonOperand = aComparisonTerm.operand(); @@ -650,19 +751,24 @@ private SelectBuilder constructSparqlWhere(final String prefixes, final String w switch (operandType) { case DECIMAL: DecimalValue decimalOperand = (DecimalValue) comparisonOperand; - distinctResourcesQuery.addWhere("?s", predicate, decimalOperand.value()); + distinctResourcesQuery.addWhere( + "?s", predicate, decimalOperand.value()); break; case STRING: StringValue stringOperand = (StringValue) comparisonOperand; - distinctResourcesQuery.addWhere("?s", predicate, "\"" + stringOperand.value() + "\""); + distinctResourcesQuery.addWhere( + "?s", predicate, "\"" + stringOperand.value() + "\""); break; case URI_REF: UriRefValue uriOperand = (UriRefValue) comparisonOperand; - distinctResourcesQuery.addWhere("?s", predicate, new ResourceImpl(uriOperand.value())); + distinctResourcesQuery.addWhere( + "?s", predicate, new ResourceImpl(uriOperand.value())); break; default: - throw new UnsupportedOperationException("only support for terms of type Comparisons," + - " where the operator is 'EQUALS', and the operand is either a String, an Integer or a URI"); + throw new UnsupportedOperationException( + "only support for terms of type Comparisons, where the operator" + + " is 'EQUALS', and the operand is either a String, an" + + " Integer or a URI"); } } } @@ -670,16 +776,16 @@ private SelectBuilder constructSparqlWhere(final String prefixes, final String w throw new IllegalArgumentException("whereExpression could not be parsed", e); } - //Setup searchTerms - //Add a sparql filter "FILTER regex(?o, "", "i")" to the distinctResourcesQuery + // Setup searchTerms + // Add a sparql filter "FILTER regex(?o, "", "i")" to the + // distinctResourcesQuery if (!StringUtils.isNullOrEmpty(searchTerms)) { ExprFactory factory = new ExprFactory(); E_Regex regex = factory.regex(factory.str("?o"), searchTerms, "i"); distinctResourcesQuery.addFilter(regex); } - - if ((limit > 0 || offset > 0) && (! OSLC4JUtils.isLyoStorePagingUnsafe())) { + if ((limit > 0 || offset > 0) && (!OSLC4JUtils.isLyoStorePagingUnsafe())) { distinctResourcesQuery.addOrderBy("?s", Order.ASCENDING); } @@ -691,10 +797,8 @@ private SelectBuilder constructSparqlWhere(final String prefixes, final String w } SelectBuilder constructSelectQuery = new SelectBuilder(); - constructSelectQuery.addVar( "s p o" ) - .addSubQuery(distinctResourcesQuery); + constructSelectQuery.addVar("s p o").addSubQuery(distinctResourcesQuery); return constructSelectQuery; } - } diff --git a/store/store-core/src/test/java/org/eclipse/lyo/store/SparqlStoreImplTest.java b/store/store-core/src/test/java/org/eclipse/lyo/store/SparqlStoreImplTest.java index 653d3d1e3..853c5e96f 100644 --- a/store/store-core/src/test/java/org/eclipse/lyo/store/SparqlStoreImplTest.java +++ b/store/store-core/src/test/java/org/eclipse/lyo/store/SparqlStoreImplTest.java @@ -11,9 +11,7 @@ import java.util.Collections; import java.util.Date; import java.util.List; - import javax.xml.datatype.DatatypeConfigurationException; - import org.apache.jena.rdf.model.Model; import org.eclipse.lyo.oslc4j.core.exception.OslcCoreApplicationException; import org.eclipse.lyo.oslc4j.core.model.ServiceProvider; @@ -58,7 +56,8 @@ public void storeBasicOps() { sp.setCreated(new Date()); try { manager.putResources(testNg, Collections.singletonList(sp)); - final List providers = manager.getResources(testNg, ServiceProvider.class); + final List providers = + manager.getResources(testNg, ServiceProvider.class); assertThat(providers).hasSize(1); } catch (StoreAccessException | ModelUnmarshallingException e) { fail("Store failed", e); @@ -77,12 +76,17 @@ public void testInsertionPerf() { fail("Store failed", e); } } - System.out.printf("10 named graphs persisted (resources) in %s ms", Duration.between(start, Instant.now()).toMillis()); + System.out.printf( + "10 named graphs persisted (resources) in %s ms", + Duration.between(start, Instant.now()).toMillis()); } @Test - public void testInsertionPerfRaw() throws InvocationTargetException, DatatypeConfigurationException, - OslcCoreApplicationException, IllegalAccessException { + public void testInsertionPerfRaw() + throws InvocationTargetException, + DatatypeConfigurationException, + OslcCoreApplicationException, + IllegalAccessException { final List providers = genProviders(); final Model jenaModel = JenaModelHelper.createJenaModel(providers.toArray()); var start = Instant.now(); @@ -90,7 +94,44 @@ public void testInsertionPerfRaw() throws InvocationTargetException, DatatypeCon final URI testNg = URI.create("urn:test:" + i); manager.insertJenaModel(testNg, jenaModel); } - System.out.printf("10 named graphs persisted (raw Model) in %s ms", Duration.between(start, Instant.now()).toMillis()); + System.out.printf( + "10 named graphs persisted (raw Model) in %s ms", + Duration.between(start, Instant.now()).toMillis()); + } + + @Test + public void testRawUpdateQuery() throws StoreAccessException { + final URI testNg = URI.create("urn:test:rawupdate"); + + // Create a graph using raw SPARQL UPDATE + String createGraphQuery = "CREATE GRAPH <" + testNg + ">"; + manager.rawUpdateQuery(createGraphQuery); + + // Verify the graph was created but is empty (should not exist according to our logic) + assertThat(manager.namedGraphExists(testNg)) + .isFalse(); // Insert some data using raw SPARQL UPDATE + String insertDataQuery = + "INSERT DATA { GRAPH <" + + testNg + + "> { \"test" + + " value\" . } }"; + manager.rawUpdateQuery(insertDataQuery); + + // Now the graph should exist because it has data + assertThat(manager.namedGraphExists(testNg)).isTrue(); + + // Query the data to verify it was inserted + Model model = + manager.getJenaModelForSubject(testNg, URI.create("http://example.org/subject")); + assertThat(model).isNotNull(); + assertThat(model.isEmpty()).isFalse(); + + // Clean up using raw SPARQL UPDATE + String dropGraphQuery = "DROP GRAPH <" + testNg + ">"; + manager.rawUpdateQuery(dropGraphQuery); + + // Verify the graph was dropped + assertThat(manager.namedGraphExists(testNg)).isFalse(); } private List genProviders() { @@ -104,6 +145,4 @@ private List genProviders() { } return providers; } - - } diff --git a/store/store-core/src/test/java/org/eclipse/lyo/store/StoreRawUpdateQueryTest.java b/store/store-core/src/test/java/org/eclipse/lyo/store/StoreRawUpdateQueryTest.java new file mode 100644 index 000000000..924b23799 --- /dev/null +++ b/store/store-core/src/test/java/org/eclipse/lyo/store/StoreRawUpdateQueryTest.java @@ -0,0 +1,216 @@ +/* + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License 1.0 + * which is available at http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ + +package org.eclipse.lyo.store; + +import static org.junit.jupiter.api.Assertions.*; + +import java.net.URI; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +@DisplayName("Store Raw Update Query Tests") +public class StoreRawUpdateQueryTest { + + private Store store; + + @BeforeEach + public void setUp() { + store = StoreFactory.sparqlInMem(); + } + + @Test + @DisplayName("Test rawUpdateQuery creates and drops graphs") + public void testRawUpdateQueryGraphOperations() throws StoreAccessException { + final URI testGraph = URI.create("http://example.org/test/graph"); + + // Create graph using raw update query + String createQuery = "CREATE GRAPH <" + testGraph + ">"; + store.rawUpdateQuery(createQuery); + + // Verify graph doesn't exist (empty graphs are not considered existing) + assertFalse( + store.namedGraphExists(testGraph), "Empty graph should not exist after creation"); + + // Drop graph using raw update query + String dropQuery = "DROP GRAPH <" + testGraph + ">"; + store.rawUpdateQuery(dropQuery); + + // Verify graph no longer exists + assertFalse(store.namedGraphExists(testGraph), "Graph should not exist after dropping"); + } + + @Test + @DisplayName("Test rawUpdateQuery inserts and deletes data") + public void testRawUpdateQueryDataOperations() throws StoreAccessException { + final URI testGraph = URI.create("http://example.org/test/data"); + final URI subject = URI.create("http://example.org/test/subject"); + + // Create graph first + String createQuery = "CREATE GRAPH <" + testGraph + ">"; + store.rawUpdateQuery(createQuery); + + // Insert data using raw update query + String insertQuery = + "INSERT DATA { " + + "GRAPH <" + + testGraph + + "> { " + + "<" + + subject + + "> " + + " . <" + + subject + + "> \"test value\" . " + + "} }"; + store.rawUpdateQuery(insertQuery); + + // Verify data exists + assertTrue( + store.resourceExists(testGraph, subject), "Resource should exist after insertion"); + + // Delete specific triples using raw update query + String deleteQuery = + "DELETE DATA { " + + "GRAPH <" + + testGraph + + "> { " + + "<" + + subject + + "> \"test value\" . " + + "} }"; + store.rawUpdateQuery(deleteQuery); + + // Verify resource still exists (type triple should remain) + assertTrue( + store.resourceExists(testGraph, subject), + "Resource should still exist after partial deletion"); + + // Delete all triples for the subject + String deleteAllQuery = + "DELETE WHERE { " + + "GRAPH <" + + testGraph + + "> { " + + "<" + + subject + + "> ?p ?o . " + + "} }"; + store.rawUpdateQuery(deleteAllQuery); + + // Verify resource no longer exists + assertFalse( + store.resourceExists(testGraph, subject), + "Resource should not exist after complete deletion"); + } + + @Test + @DisplayName("Test rawUpdateQuery with CLEAR operation") + public void testRawUpdateQueryClearOperation() throws StoreAccessException { + final URI testGraph = URI.create("http://example.org/test/clear"); + final URI subject1 = URI.create("http://example.org/test/subject1"); + final URI subject2 = URI.create("http://example.org/test/subject2"); + + // Create graph and insert multiple resources + String setupQuery = + "INSERT DATA { " + + "GRAPH <" + + testGraph + + "> { " + + "<" + + subject1 + + "> " + + " . <" + + subject2 + + "> " + + " . } }"; + store.rawUpdateQuery(setupQuery); + + // Verify resources exist + assertTrue(store.resourceExists(testGraph, subject1), "Subject1 should exist before clear"); + assertTrue(store.resourceExists(testGraph, subject2), "Subject2 should exist before clear"); + + // Clear the graph using raw update query + String clearQuery = "CLEAR GRAPH <" + testGraph + ">"; + store.rawUpdateQuery( + clearQuery); // Verify graph is empty and no longer exists (empty graphs are not + // considered existing) + assertFalse(store.namedGraphExists(testGraph), "Graph should not exist after clear"); + assertFalse( + store.resourceExists(testGraph, subject1), "Subject1 should not exist after clear"); + assertFalse( + store.resourceExists(testGraph, subject2), "Subject2 should not exist after clear"); + } + + @Test + @DisplayName("Test rawUpdateQuery with complex SPARQL update") + public void testRawUpdateQueryComplexUpdate() throws StoreAccessException { + final URI testGraph = URI.create("http://example.org/test/complex"); + + // Create a complex update that creates graph, inserts data, and modifies it + String complexQuery = + "INSERT DATA { " + + "GRAPH <" + + testGraph + + "> { " + + " " + + " . " + + " \"John Doe\" . " + + " 30 . } } ; DELETE { GRAPH <" + + testGraph + + "> { " + + "?person ?oldAge . " + + "} } " + + "INSERT { " + + "GRAPH <" + + testGraph + + "> { " + + "?person ?newAge . " + + "} } " + + "WHERE { " + + "GRAPH <" + + testGraph + + "> { " + + "?person ?oldAge . " + + "BIND(?oldAge + 1 AS ?newAge) " + + "} }"; + + store.rawUpdateQuery(complexQuery); + + // Verify the complex update worked + URI person = URI.create("http://example.org/person/1"); + assertTrue( + store.resourceExists(testGraph, person), + "Person should exist after complex update"); + + // We can't easily verify the age was incremented without querying, + // but we can verify the resource still exists and the graph is valid + assertTrue(store.namedGraphExists(testGraph), "Graph should exist after complex update"); + } + + @Test + @DisplayName("Test rawUpdateQuery handles invalid SPARQL gracefully") + public void testRawUpdateQueryInvalidSparql() { + // Test that invalid SPARQL throws an appropriate exception + String invalidQuery = "INVALID SPARQL SYNTAX"; + + assertThrows( + Exception.class, + () -> { + store.rawUpdateQuery(invalidQuery); + }, + "Invalid SPARQL should throw an exception"); + } +} diff --git a/trs/client/trs-client/pom.xml b/trs/client/trs-client/pom.xml index f1c2bf8d0..e6805fc50 100644 --- a/trs/client/trs-client/pom.xml +++ b/trs/client/trs-client/pom.xml @@ -114,19 +114,41 @@ oslc-client ${v.lyo} - - org.eclipse.rdf4j - rdf4j-repository-sparql - org.glassfish.jersey.core jersey-client + + org.eclipse.lyo.store + store-core + ${v.lyo} + compile + - junit - junit + org.junit.jupiter + junit-jupiter + test + + + org.junit.platform + junit-platform-suite + test + + + org.junit.vintage + junit-vintage-engine + test + + + org.assertj + assertj-core + test + + + org.mockito + mockito-core test diff --git a/trs/client/trs-client/src/main/java/org/eclipse/lyo/trs/client/config/TrsConfigurationLoader.java b/trs/client/trs-client/src/main/java/org/eclipse/lyo/trs/client/config/TrsConfigurationLoader.java index a31c15868..dbdee7f1e 100644 --- a/trs/client/trs-client/src/main/java/org/eclipse/lyo/trs/client/config/TrsConfigurationLoader.java +++ b/trs/client/trs-client/src/main/java/org/eclipse/lyo/trs/client/config/TrsConfigurationLoader.java @@ -14,8 +14,6 @@ package org.eclipse.lyo.trs.client.config; -import com.google.common.base.Strings; - import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; @@ -23,6 +21,7 @@ import java.io.InputStream; import java.net.URI; import java.util.Properties; +import org.eclipse.lyo.core.util.StringUtils; /** * Loads TRS Provider configuration from a .properties file. @@ -37,24 +36,24 @@ * @since 4.0.0 */ public class TrsConfigurationLoader { - public static TrsProviderConfiguration from(File f) throws IOException { - if (f == null) { - throw new IllegalArgumentException("File is null"); - } + public static TrsProviderConfiguration from(File f) throws IOException { + if (f == null) { + throw new IllegalArgumentException("File is null"); + } - try (InputStream input = new BufferedInputStream(new FileInputStream(f))) { - Properties p = new Properties(); - p.load(input); + try (InputStream input = new BufferedInputStream(new FileInputStream(f))) { + Properties p = new Properties(); + p.load(input); - String trsUriParam = p.getProperty("trs_uri"); - if (Strings.isNullOrEmpty(trsUriParam)) { - throw new IllegalStateException("The 'trs_uri' field is missing in file " + f.getName()); - } + String trsUriParam = p.getProperty("trs_uri"); + if (StringUtils.isNullOrEmpty(trsUriParam)) { + throw new IllegalStateException("The 'trs_uri' field is missing in file " + f.getName()); + } - String user = p.getProperty("baseAuth_user"); - String pass = p.getProperty("baseAuth_pwd"); + String user = p.getProperty("baseAuth_user"); + String pass = p.getProperty("baseAuth_pwd"); - return new TrsProviderConfiguration(URI.create(trsUriParam), user, pass); - } + return new TrsProviderConfiguration(URI.create(trsUriParam), user, pass); } + } } diff --git a/trs/client/trs-client/src/main/java/org/eclipse/lyo/trs/client/config/TrsConsumerConfiguration.java b/trs/client/trs-client/src/main/java/org/eclipse/lyo/trs/client/config/TrsConsumerConfiguration.java index 2396b5c8e..3112bdfaa 100644 --- a/trs/client/trs-client/src/main/java/org/eclipse/lyo/trs/client/config/TrsConsumerConfiguration.java +++ b/trs/client/trs-client/src/main/java/org/eclipse/lyo/trs/client/config/TrsConsumerConfiguration.java @@ -14,67 +14,68 @@ package org.eclipse.lyo.trs.client.config; +import jakarta.ws.rs.client.ClientBuilder; import java.util.concurrent.ScheduledExecutorService; - import org.eclipse.lyo.client.OslcClient; +import org.eclipse.lyo.core.util.StringUtils; import org.glassfish.jersey.client.authentication.HttpAuthenticationFeature; -import com.google.common.base.Strings; - -import jakarta.ws.rs.client.ClientBuilder; - public class TrsConsumerConfiguration { - private final String sparqlQueryUrl; - private final String sparqlUpdateUrl; - private final String sparqlUsername; - private final String sparqlPassword; - private final ScheduledExecutorService scheduler; - private final String basicUsername; - private final String basicPassword; - private OslcClient httpClient; + private final String sparqlQueryUrl; + private final String sparqlUpdateUrl; + private final String sparqlUsername; + private final String sparqlPassword; + private final ScheduledExecutorService scheduler; + private final String basicUsername; + private final String basicPassword; + private OslcClient httpClient; - public TrsConsumerConfiguration(final String sparqlQueryUrl, final String sparqlUpdateUrl, - final String sparqlUsername, final String sparqlPassword, - final ScheduledExecutorService scheduler, final String basicUsername, - final String basicPassword) { - this.sparqlQueryUrl = sparqlQueryUrl; - this.sparqlUpdateUrl = sparqlUpdateUrl; - this.sparqlUsername = sparqlUsername; - this.sparqlPassword = sparqlPassword; - this.scheduler = scheduler; - this.basicUsername = basicUsername; - this.basicPassword = basicPassword; - } + public TrsConsumerConfiguration( + final String sparqlQueryUrl, + final String sparqlUpdateUrl, + final String sparqlUsername, + final String sparqlPassword, + final ScheduledExecutorService scheduler, + final String basicUsername, + final String basicPassword) { + this.sparqlQueryUrl = sparqlQueryUrl; + this.sparqlUpdateUrl = sparqlUpdateUrl; + this.sparqlUsername = sparqlUsername; + this.sparqlPassword = sparqlPassword; + this.scheduler = scheduler; + this.basicUsername = basicUsername; + this.basicPassword = basicPassword; + } - public ScheduledExecutorService getScheduler() { - return scheduler; - } + public ScheduledExecutorService getScheduler() { + return scheduler; + } - public String getSparqlQueryUrl() { - return sparqlQueryUrl; - } + public String getSparqlQueryUrl() { + return sparqlQueryUrl; + } - public String getSparqlUpdateUrl() { - return sparqlUpdateUrl; - } + public String getSparqlUpdateUrl() { + return sparqlUpdateUrl; + } - public String getSparqlUsername() { - return sparqlUsername; - } + public String getSparqlUsername() { + return sparqlUsername; + } - public String getSparqlPassword() { - return sparqlPassword; - } + public String getSparqlPassword() { + return sparqlPassword; + } - // TODO Andrew@2019-07-15: create a client factory per domain or something similar - public OslcClient getHttpClient() { - if (httpClient == null) { - final ClientBuilder builder = ClientBuilder.newBuilder(); - if (!Strings.isNullOrEmpty(basicUsername)) { - builder.register(HttpAuthenticationFeature.basic(basicUsername, basicPassword)); - } - httpClient = new OslcClient(builder); - } - return httpClient; + // TODO Andrew@2019-07-15: create a client factory per domain or something similar + public OslcClient getHttpClient() { + if (httpClient == null) { + final ClientBuilder builder = ClientBuilder.newBuilder(); + if (!StringUtils.isNullOrEmpty(basicUsername)) { + builder.register(HttpAuthenticationFeature.basic(basicUsername, basicPassword)); + } + httpClient = new OslcClient(builder); } + return httpClient; + } } diff --git a/trs/client/trs-client/src/main/java/org/eclipse/lyo/trs/client/handlers/sparql/SparqlBatchingHandler.java b/trs/client/trs-client/src/main/java/org/eclipse/lyo/trs/client/handlers/sparql/SparqlBatchingHandler.java index 93f50a07a..6c8584b3d 100644 --- a/trs/client/trs-client/src/main/java/org/eclipse/lyo/trs/client/handlers/sparql/SparqlBatchingHandler.java +++ b/trs/client/trs-client/src/main/java/org/eclipse/lyo/trs/client/handlers/sparql/SparqlBatchingHandler.java @@ -4,6 +4,9 @@ import java.util.List; import org.eclipse.lyo.core.trs.ChangeEvent; import org.eclipse.lyo.core.trs.Deletion; +import org.eclipse.lyo.store.Store; +import org.eclipse.lyo.store.StoreAccessException; +import org.eclipse.lyo.store.StoreFactory; import org.eclipse.lyo.trs.client.handlers.IProviderEventHandler; import org.eclipse.lyo.trs.client.model.BaseMember; import org.eclipse.lyo.trs.client.model.ChangeEventMessageTR; @@ -12,82 +15,92 @@ import org.slf4j.LoggerFactory; public class SparqlBatchingHandler implements IProviderEventHandler { - private final static Logger log = LoggerFactory.getLogger( - SparqlBatchingHandler.class); + private static final Logger log = LoggerFactory.getLogger(SparqlBatchingHandler.class); - private final List queries = new ArrayList<>(); - private final String sparqlUpdateService; - private final String sparql_baseAuth_userName; - private final String sparql_baseAuth_pwd; + private final List queries = new ArrayList<>(); + private final String sparqlUpdateService; + private final String sparql_baseAuth_userName; + private final String sparql_baseAuth_pwd; - public SparqlBatchingHandler(final String sparqlUpdateService, - final String sparql_baseAuth_userName, final String sparql_baseAuth_pwd) { - this.sparqlUpdateService = sparqlUpdateService; - this.sparql_baseAuth_userName = sparql_baseAuth_userName; - this.sparql_baseAuth_pwd = sparql_baseAuth_pwd; - } - - @Override - public void finishCycle() { - log.debug("number of processed queries: " + queries.size()); - String finalQueryString = buildYugeQuery(queries); - log.debug("sending Update SPARQL Query to server"); + public SparqlBatchingHandler( + final String sparqlUpdateService, + final String sparql_baseAuth_userName, + final String sparql_baseAuth_pwd) { + this.sparqlUpdateService = sparqlUpdateService; + this.sparql_baseAuth_userName = sparql_baseAuth_userName; + this.sparql_baseAuth_pwd = sparql_baseAuth_pwd; + } - SparqlUtil.processQuery_sesame(finalQueryString, sparqlUpdateService, - sparql_baseAuth_userName, sparql_baseAuth_pwd); - log.debug("Update SPARQL Queries successful!"); + @Override + public void finishCycle() { + log.debug("number of processed queries: " + queries.size()); + String updateQuery = buildYugeQuery(queries); + log.debug("sending Update SPARQL Query to server"); - queries.clear(); + // TODO: build one or use a pool + Store store = + StoreFactory.sparql( + null, sparqlUpdateService, sparql_baseAuth_userName, sparql_baseAuth_pwd); + try { + store.rawUpdateQuery(updateQuery); + } catch (StoreAccessException e) { + throw new RuntimeException(e); + } finally { + store.close(); } - @Override - public void handleBaseMember(final BaseMember baseMember) { - StringBuilder query = new StringBuilder(); - String graphCreationQuery = SparqlUtil.createGraphQuery(baseMember.getUri()); - String addTriplesToGraphQuery = SparqlUtil.addTriplesToGraphQuery(baseMember.getUri(), - baseMember.getModel()); - query.append(graphCreationQuery); - query.append("; \n"); - query.append(addTriplesToGraphQuery); - queries.add(query.toString()); - } + log.debug("Update SPARQL Queries successful!"); - @Override - public void handleChangeEvent(final ChangeEventMessageTR eventMessageTR) { - final ChangeEvent event = eventMessageTR.getChangeEvent(); - log.debug( - "creating query for resource " + event.getChanged().toString() + " change event "); - if (event instanceof Deletion) { - String query = SparqlUtil.getChangeEventQuery(event, null); - queries.add(query); - } else { - String query = SparqlUtil.getChangeEventQuery(event, - eventMessageTR.getTrackedResourceModel()); - queries.add(query); - } - } + queries.clear(); + } - @Override - public void rebase() { - log.warn("Rebase"); - } + @Override + public void handleBaseMember(final BaseMember baseMember) { + StringBuilder query = new StringBuilder(); + String graphCreationQuery = SparqlUtil.createGraphQuery(baseMember.getUri()); + String addTriplesToGraphQuery = + SparqlUtil.addTriplesToGraphQuery(baseMember.getUri(), baseMember.getModel()); + query.append(graphCreationQuery); + query.append("; \n"); + query.append(addTriplesToGraphQuery); + queries.add(query.toString()); + } - private String buildYugeQuery(final List queries) { - StringBuilder queriesStringBuilder = new StringBuilder(); + @Override + public void handleChangeEvent(final ChangeEventMessageTR eventMessageTR) { + final ChangeEvent event = eventMessageTR.getChangeEvent(); + log.debug("creating query for resource " + event.getChanged().toString() + " change event "); + if (event instanceof Deletion) { + String query = SparqlUtil.getChangeEventQuery(event, null); + queries.add(query); + } else { + String query = + SparqlUtil.getChangeEventQuery(event, eventMessageTR.getTrackedResourceModel()); + queries.add(query); + } + } - for (String query : queries) { - queriesStringBuilder.append(query); - queriesStringBuilder.append("; \n"); - } + @Override + public void rebase() { + log.warn("Rebase"); + } -// TODO simply join instead of append - or check for the last element - queriesStringBuilder.replace(queriesStringBuilder.lastIndexOf("; \n"), - queriesStringBuilder.lastIndexOf("; \n") + 1, ""); + private String buildYugeQuery(final List queries) { + StringBuilder queriesStringBuilder = new StringBuilder(); - // TODO Andrew@2018-02-28: this is a YUGE query that can crash everything - // I think individual queries are better executed in the handlers - String finalQueryString = queriesStringBuilder.toString(); - log.debug(finalQueryString); - return finalQueryString; + for (String query : queries) { + queriesStringBuilder.append(query); + queriesStringBuilder.append("; \n"); } + + // TODO simply join instead of append - or check for the last element + queriesStringBuilder.replace( + queriesStringBuilder.lastIndexOf("; \n"), queriesStringBuilder.lastIndexOf("; \n") + 1, ""); + + // TODO Andrew@2018-02-28: this is a YUGE query that can crash everything + // I think individual queries are better executed in the handlers + String finalQueryString = queriesStringBuilder.toString(); + log.debug(finalQueryString); + return finalQueryString; + } } diff --git a/trs/client/trs-client/src/main/java/org/eclipse/lyo/trs/client/util/SparqlUtil.java b/trs/client/trs-client/src/main/java/org/eclipse/lyo/trs/client/util/SparqlUtil.java index 31c708368..9d6b07f34 100644 --- a/trs/client/trs-client/src/main/java/org/eclipse/lyo/trs/client/util/SparqlUtil.java +++ b/trs/client/trs-client/src/main/java/org/eclipse/lyo/trs/client/util/SparqlUtil.java @@ -15,7 +15,6 @@ import java.io.IOException; import java.net.URI; - import org.apache.jena.rdf.model.Model; import org.apache.jena.update.UpdateExecutionFactory; import org.apache.jena.update.UpdateFactory; @@ -25,12 +24,6 @@ import org.eclipse.lyo.core.trs.Creation; import org.eclipse.lyo.core.trs.Deletion; import org.eclipse.lyo.core.trs.Modification; -import org.eclipse.rdf4j.query.QueryLanguage; -import org.eclipse.rdf4j.query.TupleQueryResult; -import org.eclipse.rdf4j.query.Update; -import org.eclipse.rdf4j.repository.RepositoryConnection; -import org.eclipse.rdf4j.repository.RepositoryException; -import org.eclipse.rdf4j.repository.sparql.SPARQLRepository; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -48,563 +41,616 @@ */ public class SparqlUtil { - static Logger logger = LoggerFactory.getLogger(SparqlUtil.class); - - /** - * returns the sparql query for the creation of the named graph with the - * given name as a String - * - * @param namedGraphUrl - * name of the named graph to be created - * @return the graph creation sparql query as a string - */ - static public String createGraphQuery(String namedGraphUrl) { - String query = "CREATE GRAPH <" + namedGraphUrl + ">"; - logger.debug("query for creation of graph: " + namedGraphUrl); - logger.debug(query); - return query; - } - - static public String createGraphQuery(URI namedGraphUrl) { - return createGraphQuery(namedGraphUrl.toASCIIString()); - } - - /** - * returns the query deleting siltently the graph with the given name - * - * @param namedGraphUrl - * the name of the graph to be deleted - * @return the deletion query as a string - */ - static public String dropGraphQuery(String namedGraphUrl) { - String query = "DROP GRAPH <" + namedGraphUrl + ">"; - logger.debug("query for removal of graph: " + namedGraphUrl); - logger.debug(query); - return query; - } - - /** - * returns the sparql query removing all the triples in the requested graph - * as a string - * - * @param namedGraphUrl - * graph to be emptied - * @return the query for emptying the graph - */ - static public String removeAllTriplesInGraphQuery(String namedGraphUrl) { - String query = "WITH" + " " + "<" + namedGraphUrl + ">" + "\n" + "DELETE" + "\n" + "{?s ?p ?o }" + "\n" - + "WHERE" + "\n" + "{" + "\n" + "GRAPH" + " " + "<" + namedGraphUrl + ">" + "\n" + "{" + "\n" - + "?s ?p ?o" + "\n" + "}" + "\n" + "}" + "\n"; - logger.debug("query for removal of all triples from graph: " + namedGraphUrl); - logger.debug(query); - return query; - } - - /** - * Gets an rdf model and a named graph as arguments and returns a sparql - * query for adding the statements of the rdf model to the named graph - * - * @param namedGraphUrl - * named graph to which the statements shall be added - * @param jenaModel - * the rdf model of which statements are to be added to the named - * graph - * @return the sparql update - */ - static public String addTriplesToGraphQuery(String namedGraphUrl, Model jenaModel) { - try { - String nTripleRepresentation = RdfUtil.modelToNTriple(jenaModel); - return addTriplesToGraphQuery(namedGraphUrl, nTripleRepresentation); - } catch (IOException e) { - logger.error("Cannot append triples from the model to the query", e); - return null; - } - } - - static public String addTriplesToGraphQuery(URI namedGraphUrl, Model jenaModel) { - return addTriplesToGraphQuery(namedGraphUrl.toASCIIString(), jenaModel); - } - - - /** - * Gets a set of statements and a named graph as arguments and returns a - * sparql query for adding the statements to the named graph - * - * @param namedGraphUrl - * named graph to which the statements shall be added - * @param triples - * statements to be added to the named graph - * @return the sparql update - */ - static public String addTriplesToGraphQuery(String namedGraphUrl, String triples) { - String query = "INSERT DATA" + "\n" + "{" + "\n" + " GRAPH" + " " + "<" + namedGraphUrl + ">" + "\n" + "{" - + "\n" + triples + "\n" + "}" + "\n" + "}"; - logger.debug("query for creation of triples in graph: " + namedGraphUrl); - logger.debug(query); - return query; + static Logger logger = LoggerFactory.getLogger(SparqlUtil.class); + + /** + * returns the sparql query for the creation of the named graph with the + * given name as a String + * + * @param namedGraphUrl + * name of the named graph to be created + * @return the graph creation sparql query as a string + */ + public static String createGraphQuery(String namedGraphUrl) { + String query = "CREATE GRAPH <" + namedGraphUrl + ">"; + logger.debug("query for creation of graph: " + namedGraphUrl); + logger.debug(query); + return query; + } + + public static String createGraphQuery(URI namedGraphUrl) { + return createGraphQuery(namedGraphUrl.toASCIIString()); + } + + /** + * returns the query deleting siltently the graph with the given name + * + * @param namedGraphUrl + * the name of the graph to be deleted + * @return the deletion query as a string + */ + public static String dropGraphQuery(String namedGraphUrl) { + String query = "DROP GRAPH <" + namedGraphUrl + ">"; + logger.debug("query for removal of graph: " + namedGraphUrl); + logger.debug(query); + return query; + } + + /** + * returns the sparql query removing all the triples in the requested graph + * as a string + * + * @param namedGraphUrl + * graph to be emptied + * @return the query for emptying the graph + */ + public static String removeAllTriplesInGraphQuery(String namedGraphUrl) { + String query = + "WITH" + + " " + + "<" + + namedGraphUrl + + ">" + + "\n" + + "DELETE" + + "\n" + + "{?s ?p ?o }" + + "\n" + + "WHERE" + + "\n" + + "{" + + "\n" + + "GRAPH" + + " " + + "<" + + namedGraphUrl + + ">" + + "\n" + + "{" + + "\n" + + "?s ?p ?o" + + "\n" + + "}" + + "\n" + + "}" + + "\n"; + logger.debug("query for removal of all triples from graph: " + namedGraphUrl); + logger.debug(query); + return query; + } + + /** + * Gets an rdf model and a named graph as arguments and returns a sparql + * query for adding the statements of the rdf model to the named graph + * + * @param namedGraphUrl + * named graph to which the statements shall be added + * @param jenaModel + * the rdf model of which statements are to be added to the named + * graph + * @return the sparql update + */ + public static String addTriplesToGraphQuery(String namedGraphUrl, Model jenaModel) { + try { + String nTripleRepresentation = RdfUtil.modelToNTriple(jenaModel); + return addTriplesToGraphQuery(namedGraphUrl, nTripleRepresentation); + } catch (IOException e) { + logger.error("Cannot append triples from the model to the query", e); + return null; } - - /** - * For a change event return a sparql update relfecting the change event. - * - * @param changeEvent - * the change event for which the sparql update is created - * @param model - * the rdf model corresponding to the updated representation of - * the changed resource if applicable ( no rdf model is needed - * for a deletion event) - * @return the sparql update - */ - static public String getChangeEventQuery(ChangeEvent changeEvent, Model model) { - - if (changeEvent instanceof Creation) { - return getCreationEventQuery(changeEvent, model); - } else if (changeEvent instanceof Deletion) { - return getDeletionEventQuery(changeEvent); - } else if (changeEvent instanceof Modification) { - return getModificationEventQuery(changeEvent, model); - } - return null; - + } + + public static String addTriplesToGraphQuery(URI namedGraphUrl, Model jenaModel) { + return addTriplesToGraphQuery(namedGraphUrl.toASCIIString(), jenaModel); + } + + /** + * Gets a set of statements and a named graph as arguments and returns a + * sparql query for adding the statements to the named graph + * + * @param namedGraphUrl + * named graph to which the statements shall be added + * @param triples + * statements to be added to the named graph + * @return the sparql update + */ + public static String addTriplesToGraphQuery(String namedGraphUrl, String triples) { + String query = + "INSERT DATA" + + "\n" + + "{" + + "\n" + + " GRAPH" + + " " + + "<" + + namedGraphUrl + + ">" + + "\n" + + "{" + + "\n" + + triples + + "\n" + + "}" + + "\n" + + "}"; + logger.debug("query for creation of triples in graph: " + namedGraphUrl); + logger.debug(query); + return query; + } + + /** + * For a change event return a sparql update relfecting the change event. + * + * @param changeEvent + * the change event for which the sparql update is created + * @param model + * the rdf model corresponding to the updated representation of + * the changed resource if applicable ( no rdf model is needed + * for a deletion event) + * @return the sparql update + */ + public static String getChangeEventQuery(ChangeEvent changeEvent, Model model) { + + if (changeEvent instanceof Creation) { + return getCreationEventQuery(changeEvent, model); + } else if (changeEvent instanceof Deletion) { + return getDeletionEventQuery(changeEvent); + } else if (changeEvent instanceof Modification) { + return getModificationEventQuery(changeEvent, model); } - - /** - * For a modification event return a sparql update relfecting the change - * event. - * - * @param changeEvent - * the change event for which the sparql update is created - * @param model - * the rdf model corresponding to the updated representation of - * the changed resource - * @return the sparql update - */ - static public String getModificationEventQuery(ChangeEvent changeEvent, Model model) { - String result = ""; - String changeEventTarget = changeEvent.getChanged().toString(); - String dropGraphQuery = dropGraphQuery(changeEventTarget); - String addGraphQuery = createGraphQuery(changeEventTarget); - String addTriplesToNamedGraphQuery = addTriplesToGraphQuery(changeEventTarget, model); - - result = result.concat(dropGraphQuery); - result = result.concat(";" + "\n"); - result = result.concat(addGraphQuery); - result = result.concat(";" + "\n"); - result = result.concat(addTriplesToNamedGraphQuery); - - return result; - } - - /** - * For a modification event return a sparql update relfecting the change - * event. - * - * @param changeEvent - * the change event for which the sparql update is created - * @param triples - * the rdf model in n triples corresponding to the updated - * representation of the changed resource - * @return the sparql update - */ - static public String getModificationEventQuery(ChangeEvent changeEvent, String triples) { - - String changeEventTarget = changeEvent.getChanged().toString(); - return getModificationEventQuery(changeEventTarget, triples); - } - - /** - * For a modification event target return a sparql update relfecting the - * change event. - * - * @param changeEventTarget - * the change resource for which the sparql update is created - * @param triples - * the rdf model in n triples corresponding to the updated - * representation of the changed resource - * @return the sparql update - */ - static public String getModificationEventQuery(String changeEventTarget, String triples) { - String result = ""; - String dropGraphQuery = dropGraphQuery(changeEventTarget); - String addGraphQuery = createGraphQuery(changeEventTarget); - String addTriplesToNamedGraphQuery = addTriplesToGraphQuery(changeEventTarget, triples); - - result = result.concat(dropGraphQuery); - result = result.concat(";" + "\n"); - result = result.concat(addGraphQuery); - result = result.concat(";" + "\n"); - result = result.concat(addTriplesToNamedGraphQuery); - - return result; - } - - /** - * * For a creation event return a sparql update reflecting the change - * event. - * - * @param changeEvent - * change event for which update will be created - * @param model - * updated rdf representation of the changed resource - * @return - */ - static public String getCreationEventQuery(ChangeEvent changeEvent, Model model) { - String result = ""; - - String changeEventTarget = changeEvent.getChanged().toString(); - String addGraphQuery = createGraphQuery(changeEventTarget); - String addTriplesToNamedGraphQuery = addTriplesToGraphQuery(changeEventTarget, model); - - result = result.concat(addGraphQuery); - result = result.concat(";" + "\n"); - result = result.concat(addTriplesToNamedGraphQuery); - - return result; - } - - /** - * For a deletion event return a sparql update relfecting the change event. - * - * @param changeEvent - * the deletion change event for which the sparql update is - * created - * @return the sparql update - */ - static public String getDeletionEventQuery(ChangeEvent changeEvent) { - String result = ""; - - String changeEventTarget = changeEvent.getChanged().toString(); - - String dropGraphQuery = dropGraphQuery(changeEventTarget); - - result = result.concat(dropGraphQuery); - - return result; - } - - /** - * create a graph creation of the graph with the specified name sparql - * update and post it to the given service url - * - * @param namedGraphUrl - * name of the graph to be created - * @param serviceUrl - * sparql update endpoint url - */ - static public void createGraph(String namedGraphUrl, String serviceUrl) { - UpdateRequest request = UpdateFactory.create(); - request.add(createGraphQuery(namedGraphUrl)); - UpdateProcessor processor = UpdateExecutionFactory.createRemote(request, serviceUrl); - processor.execute(); - } - - /** - * create a graph creation of the graph with the specified name sparql - * update and post it to the given service url - * - * @param namedGraphUrl - * name of the graph to be deleted - * @param serviceUrl - * sparql update endpoint url - */ - static public void dropGraph(String namedGraphUrl, String serviceUrl) { - UpdateRequest request = UpdateFactory.create(); - request.add(dropGraphQuery(namedGraphUrl)); - UpdateProcessor processor = UpdateExecutionFactory.createRemote(request, serviceUrl); - processor.execute(); - } - - /** - * create a spaql update adding the triples in the given rdf model to the - * graph with the given name and send it to the specified sparql update - * endpoint - * - * @param jenaModel - * the triples to be added to the named graph - * @param namedGraphUrl - * the named graph to which the triples shall be added - * @param serviceUrl - * the sparql update endpoint - */ - static public void addTriplesToNamedGraph(Model jenaModel, String namedGraphUrl, String serviceUrl) { - UpdateRequest request = UpdateFactory.create(); - request.add(addTriplesToGraphQuery(namedGraphUrl, jenaModel)); - UpdateProcessor processor = UpdateExecutionFactory.createRemote(request, serviceUrl); - processor.execute(); - } - - /** - * create a spaql update removing all triples from the graph with the given - * name and send it to the specified sparql update endpoint - * - * @param namedGraphUrl - * the named graph that shall be emptied - * @param serviceUrl - * the sparql update endpoint - */ - static public void removeAllTriplesInNamedGraph(String namedGraphUrl, String serviceUrl) { - UpdateRequest request = UpdateFactory.create(); - request.add(removeAllTriplesInGraphQuery(namedGraphUrl)); - UpdateProcessor processor = UpdateExecutionFactory.createRemote(request, serviceUrl); - processor.execute(); - } - - /** - * create a spaql update reflecting the given change event and send it to - * the sparql update endpoint - * - * @param changeEvent - * the change event to be processed - * @param model - * the updated representation of the changed resource if - * applicable - * @param serviceUrl - * the sparql update endpoint - */ - static public void processChangeEvent(ChangeEvent changeEvent, Model model, String serviceUrl) { - String changeEventQuery = getChangeEventQuery(changeEvent, model); - processQuery(changeEventQuery, serviceUrl); - } - - /** - * Send the given sparql update to the sparql update service using the jena - * arq libraries - * - * @param query - * sparql update to be processeda - * @param serviceUrl - * sparql update endpoint for processing the sparql update - */ - static public void processQuery(String query, String serviceUrl) { - UpdateRequest request = UpdateFactory.create(); - request.add(query); - UpdateProcessor processor = UpdateExecutionFactory.createRemote(request, serviceUrl); - processor.execute(); - } - - /** - * Send the given sparql update to the sparql update service using the - * sesame libraries - * - * @param query - * sparql update to be processeda - * @param serviceUrl - * sparql update endpoint for processing the sparql update - * @param user - * username for authentication if applicable - * @param pwd - * password for authentication if applicable - */ - static public void processQuery_sesame(String query, String serviceUrl, String user, String pwd) { - SPARQLRepository repo = new SPARQLRepository(serviceUrl); - repo.setUsernameAndPassword(user, pwd); - repo.init(); - RepositoryConnection rc = repo.getConnection(); - processQuery_sesame(query, rc); - } - - /** - * Send the given sparql update to the sparql update service using the - * sesame libraries - * - * @param query - * sparql update to be processeda - * @param conn - * the repository connection object holding credentials and the - * sparql update endpoint - */ - static public void processQuery_sesame(String query, RepositoryConnection conn) { - Update u = conn.prepareUpdate(query); - u.execute(); - } - - /** - * return the repo connection object in order to be able to use the sesame - * client libraries - * - * @param queryEndpoint - * the sparl query endpoint - * @param user - * username for authentication if applicable - * @param pwd - * password for authentication if applicable - * @return - */ - public static RepositoryConnection getRepoConnection(String queryEndpoint, String user, String pwd) { - SPARQLRepository repo = new SPARQLRepository(queryEndpoint); - if (user != null && pwd != null && !user.isEmpty() && !pwd.isEmpty()) { - repo.setUsernameAndPassword(user, pwd); - } - repo.init(); - try { - RepositoryConnection conn = repo.getConnection(); - return conn; - } catch (RepositoryException e) { - logger.error("error getting sparql repo connection !", e); - return null; - } - } - - /** - * return the repo connection object in order to be able to use the sesame - * client libraries - * - * @param queryEndpoint - * the sparl query endpoint - * @param user - * username for authentication if applicable - * @param pwd - * password for authentication if applicable - * @return - */ - public static RepositoryConnection getRepoConnection(String queryEndpoint, String updateEndPoint, String user, - String pwd) { - SPARQLRepository repo = new SPARQLRepository(queryEndpoint, updateEndPoint); - if (user != null && pwd != null && !user.isEmpty() && !pwd.isEmpty()) { - repo.setUsernameAndPassword(user, pwd); - } - repo.init(); - try { - RepositoryConnection conn = repo.getConnection(); - return conn; - } catch (RepositoryException e) { - logger.error("error getting sparql repo connection !", e); - return null; - } - } - - /** - * evaluate the given sparql query against the given sparql query endpoint - * - * @param queryEndpoint - * sparql query endpoint - * @param user - * username for authentication if applicable - * @param pwd - * password for authentication if applicable - * @param query - * sparql query - * @return the result of the querie's evaluation - */ - public static TupleQueryResult evalQuery(String queryEndpoint, String user, String pwd, String query) { - RepositoryConnection conn = getRepoConnection(queryEndpoint, user, pwd, query); - TupleQueryResult result = null; - try { - - result = conn.prepareTupleQuery(QueryLanguage.SPARQL, query).evaluate(); - } catch (Exception e) { - logger.error("error during the execution of the query !", e); - } finally { - conn.close(); - } - return result; - } - - /** - * evaluate the given sparql update using the sesame repository connection - * object - * - * @param conn - * repo connection sesame object - * @param sparqlQuery - * sparql update to evaluate - */ - public static void evalUpdate(RepositoryConnection conn, String sparqlQuery) { - try { - - conn.prepareUpdate(QueryLanguage.SPARQL, sparqlQuery).execute(); - } catch (Exception e) { - logger.error("error during the execution of the query !", e); - } - } - - /** - * evaluate the given sparql query using the sesame repository connection - * object - * - * @param conn - * repo connection sesame object - * @param sparqlQuery - * sparql query to evaluate - * @return the queri's evaluation result - */ - public static TupleQueryResult evalQuery(RepositoryConnection conn, String sparqlQuery) { - TupleQueryResult result = null; - try { - - result = conn.prepareTupleQuery(QueryLanguage.SPARQL, sparqlQuery).evaluate(); - - } catch (Exception e) { - logger.error("error during the execution of the query !", e); - } - - return result; - } - - /** - * append a sparql update to another - * - * @param appending - * the original sparql update - * @param appended - * the sparql update to be appended - * @return the concatnated sparql update - */ - public static String appendSparqldQuery(String appending, String appended) { - if (appending != null && !appending.isEmpty()) { - if (!appending.endsWith(";")) { - appending = appending.concat(";"); - } - - if (!appending.endsWith("\n")) { - appending = appending.concat("\n"); - } - } - - appending = appending.concat(appended); - - return appending; - } - - /** - * Create a sparql update ading the triples to the named graph with the - * specified name and send it to the sparql update endpoint specified using - * the given repo connection object. Uses the sesame libraries - * - * @param conn - * sesame repo connection object - * @param triples - * triples to be added to the named graph - * @param graphName - * named graph to which the triples shall be added - */ - public void processTripleAdditionQuery(RepositoryConnection conn, String triples, String graphName) { - String addTriplesToGraphQuery = SparqlUtil.addTriplesToGraphQuery(graphName, triples); - SparqlUtil.processQuery_sesame(addTriplesToGraphQuery, conn); - } - - /** - * Create a triple with the link type as a predicate the src as subject and - * destination as object. This is a conveniece for enabling the creation of - * links in a ageneric way - * - * @param src - * source of the link - * @param dst - * destination of the link - * @param linkType - * type of the link - * @return the rdf triple as n triple - */ - public static String linkTriple(String src, String dst, String linkType) { - StringBuilder sb = new StringBuilder(); - sb.append("<" + src + ">"); - sb.append(" "); - sb.append("<" + linkType + ">"); - sb.append(" "); - sb.append("<" + dst + ">"); - sb.append(" ."); - return sb.toString(); + return null; + } + + /** + * For a modification event return a sparql update relfecting the change + * event. + * + * @param changeEvent + * the change event for which the sparql update is created + * @param model + * the rdf model corresponding to the updated representation of + * the changed resource + * @return the sparql update + */ + public static String getModificationEventQuery(ChangeEvent changeEvent, Model model) { + String result = ""; + String changeEventTarget = changeEvent.getChanged().toString(); + String dropGraphQuery = dropGraphQuery(changeEventTarget); + String addGraphQuery = createGraphQuery(changeEventTarget); + String addTriplesToNamedGraphQuery = addTriplesToGraphQuery(changeEventTarget, model); + + result = result.concat(dropGraphQuery); + result = result.concat(";" + "\n"); + result = result.concat(addGraphQuery); + result = result.concat(";" + "\n"); + result = result.concat(addTriplesToNamedGraphQuery); + + return result; + } + + /** + * For a modification event return a sparql update relfecting the change + * event. + * + * @param changeEvent + * the change event for which the sparql update is created + * @param triples + * the rdf model in n triples corresponding to the updated + * representation of the changed resource + * @return the sparql update + */ + public static String getModificationEventQuery(ChangeEvent changeEvent, String triples) { + + String changeEventTarget = changeEvent.getChanged().toString(); + return getModificationEventQuery(changeEventTarget, triples); + } + + /** + * For a modification event target return a sparql update relfecting the + * change event. + * + * @param changeEventTarget + * the change resource for which the sparql update is created + * @param triples + * the rdf model in n triples corresponding to the updated + * representation of the changed resource + * @return the sparql update + */ + public static String getModificationEventQuery(String changeEventTarget, String triples) { + String result = ""; + String dropGraphQuery = dropGraphQuery(changeEventTarget); + String addGraphQuery = createGraphQuery(changeEventTarget); + String addTriplesToNamedGraphQuery = addTriplesToGraphQuery(changeEventTarget, triples); + + result = result.concat(dropGraphQuery); + result = result.concat(";" + "\n"); + result = result.concat(addGraphQuery); + result = result.concat(";" + "\n"); + result = result.concat(addTriplesToNamedGraphQuery); + + return result; + } + + /** + * * For a creation event return a sparql update reflecting the change + * event. + * + * @param changeEvent + * change event for which update will be created + * @param model + * updated rdf representation of the changed resource + * @return + */ + public static String getCreationEventQuery(ChangeEvent changeEvent, Model model) { + String result = ""; + + String changeEventTarget = changeEvent.getChanged().toString(); + String addGraphQuery = createGraphQuery(changeEventTarget); + String addTriplesToNamedGraphQuery = addTriplesToGraphQuery(changeEventTarget, model); + + result = result.concat(addGraphQuery); + result = result.concat(";" + "\n"); + result = result.concat(addTriplesToNamedGraphQuery); + + return result; + } + + /** + * For a deletion event return a sparql update relfecting the change event. + * + * @param changeEvent + * the deletion change event for which the sparql update is + * created + * @return the sparql update + */ + public static String getDeletionEventQuery(ChangeEvent changeEvent) { + String result = ""; + + String changeEventTarget = changeEvent.getChanged().toString(); + + String dropGraphQuery = dropGraphQuery(changeEventTarget); + + result = result.concat(dropGraphQuery); + + return result; + } + + /** + * create a graph creation of the graph with the specified name sparql + * update and post it to the given service url + * + * @param namedGraphUrl + * name of the graph to be created + * @param serviceUrl + * sparql update endpoint url + */ + public static void createGraph(String namedGraphUrl, String serviceUrl) { + UpdateRequest request = UpdateFactory.create(); + request.add(createGraphQuery(namedGraphUrl)); + UpdateProcessor processor = UpdateExecutionFactory.createRemote(request, serviceUrl); + processor.execute(); + } + + /** + * create a graph creation of the graph with the specified name sparql + * update and post it to the given service url + * + * @param namedGraphUrl + * name of the graph to be deleted + * @param serviceUrl + * sparql update endpoint url + */ + public static void dropGraph(String namedGraphUrl, String serviceUrl) { + UpdateRequest request = UpdateFactory.create(); + request.add(dropGraphQuery(namedGraphUrl)); + UpdateProcessor processor = UpdateExecutionFactory.createRemote(request, serviceUrl); + processor.execute(); + } + + /** + * create a spaql update adding the triples in the given rdf model to the + * graph with the given name and send it to the specified sparql update + * endpoint + * + * @param jenaModel + * the triples to be added to the named graph + * @param namedGraphUrl + * the named graph to which the triples shall be added + * @param serviceUrl + * the sparql update endpoint + */ + public static void addTriplesToNamedGraph( + Model jenaModel, String namedGraphUrl, String serviceUrl) { + UpdateRequest request = UpdateFactory.create(); + request.add(addTriplesToGraphQuery(namedGraphUrl, jenaModel)); + UpdateProcessor processor = UpdateExecutionFactory.createRemote(request, serviceUrl); + processor.execute(); + } + + /** + * create a spaql update removing all triples from the graph with the given + * name and send it to the specified sparql update endpoint + * + * @param namedGraphUrl + * the named graph that shall be emptied + * @param serviceUrl + * the sparql update endpoint + */ + public static void removeAllTriplesInNamedGraph(String namedGraphUrl, String serviceUrl) { + UpdateRequest request = UpdateFactory.create(); + request.add(removeAllTriplesInGraphQuery(namedGraphUrl)); + UpdateProcessor processor = UpdateExecutionFactory.createRemote(request, serviceUrl); + processor.execute(); + } + + /** + * create a spaql update reflecting the given change event and send it to + * the sparql update endpoint + * + * @param changeEvent + * the change event to be processed + * @param model + * the updated representation of the changed resource if + * applicable + * @param serviceUrl + * the sparql update endpoint + */ + public static void processChangeEvent(ChangeEvent changeEvent, Model model, String serviceUrl) { + String changeEventQuery = getChangeEventQuery(changeEvent, model); + processQuery(changeEventQuery, serviceUrl); + } + + /** + * Send the given sparql update to the sparql update service using the jena + * arq libraries + * + * @param query + * sparql update to be processeda + * @param serviceUrl + * sparql update endpoint for processing the sparql update + */ + public static void processQuery(String query, String serviceUrl) { + UpdateRequest request = UpdateFactory.create(); + request.add(query); + UpdateProcessor processor = UpdateExecutionFactory.createRemote(request, serviceUrl); + processor.execute(); + } + + /** + * Send the given sparql update to the sparql update service using the + * sesame libraries + * + * @param query + * sparql update to be processeda + * @param serviceUrl + * sparql update endpoint for processing the sparql update + * @param user + * username for authentication if applicable + * @param pwd + * password for authentication if applicable + */ + // static public void processQuery_sesame(String query, String serviceUrl, String user, + // String pwd) { + // SPARQLRepository repo = new SPARQLRepository(serviceUrl); + // repo.setUsernameAndPassword(user, pwd); + // repo.initialize(); + // RepositoryConnection rc = repo.getConnection(); + // processQuery_sesame(query, rc); + // } + + /** + * Send the given sparql update to the sparql update service using the + * sesame libraries + * + * @param query + * sparql update to be processeda + * @param conn + * the repository connection object holding credentials and the + * sparql update endpoint + */ + // static public void processQuery_sesame(String query, RepositoryConnection conn) { + // Update u = conn.prepareUpdate(query); + // u.execute(); + // } + + /** + * return the repo connection object in order to be able to use the sesame + * client libraries + * + * @param queryEndpoint + * the sparl query endpoint + * @param user + * username for authentication if applicable + * @param pwd + * password for authentication if applicable + * @return + */ + // public static RepositoryConnection getRepoConnection(String queryEndpoint, String user, + // String pwd) { + // SPARQLRepository repo = new SPARQLRepository(queryEndpoint); + // if (user != null && pwd != null && !user.isEmpty() && !pwd.isEmpty()) { + // repo.setUsernameAndPassword("okacimi", "nohheis4ae"); + // } + // repo.initialize(); + // try { + // RepositoryConnection conn = repo.getConnection(); + // if (conn == null) { + // logger.error("error getting sparql repo connection !"); + // } + // return conn; + // } catch (Exception e) { + // logger.error("error getting sparql repo connection !", e); + // return null; + // } + // } + + /** + * return the repo connection object in order to be able to use the sesame + * client libraries + * + * @param queryEndpoint + * the sparl query endpoint + * @param user + * username for authentication if applicable + * @param pwd + * password for authentication if applicable + * @return + */ + // public static RepositoryConnection getRepoConnection(String queryEndpoint, String + // updateEndPoint, String user, + // String pwd) { + // SPARQLRepository repo = new SPARQLRepository(queryEndpoint, updateEndPoint); + // if (user != null && pwd != null && !user.isEmpty() && !pwd.isEmpty() && + // !user.isEmpty()) { + // repo.setUsernameAndPassword(user, pwd); + // } + // repo.initialize(); + // try { + // RepositoryConnection conn = repo.getConnection(); + // + // if (conn == null) { + // logger.error("error getting sparql repo connection !"); + // } + // return conn; + // } catch (Exception e) { + // logger.error("error getting sparql repo connection !", e); + // return null; + // } + // } + + /** + * evaluate the given sparql query against the given sparql query endpoint + * + * @param queryEndpoint + * sparql query endpoint + * @param user + * username for authentication if applicable + * @param pwd + * password for authentication if applicable + * @param query + * sparql query + * @return the result of the querie's evaluation + */ + // public static TupleQueryResult evalQuery(String queryEndpoint, String user, String pwd, + // String query) { + // RepositoryConnection conn = getRepoConnection(queryEndpoint, user, pwd, query); + // TupleQueryResult result = null; + // try { + // + // result = conn.prepareTupleQuery(QueryLanguage.SPARQL, query).evaluate(); + // } catch (Exception e) { + // logger.error("error during the execution of the query !", e); + // } finally { + // conn.close(); + // } + // return result; + // } + + /** + * evaluate the given sparql update using the sesame repository connection + * object + * + * @param conn + * repo connection sesame object + * @param sparqlQuery + * sparql update to evaluate + */ + // public static void evalUpdate(RepositoryConnection conn, String sparqlQuery) { + // try { + // + // conn.prepareUpdate(QueryLanguage.SPARQL, sparqlQuery).execute(); + // } catch (Exception e) { + // logger.error("error during the execution of the query !", e); + // } + // } + + /** + * evaluate the given sparql query using the sesame repository connection + * object + * + * @param conn + * repo connection sesame object + * @param sparqlQuery + * sparql query to evaluate + * @return the queri's evaluation result + */ + // public static TupleQueryResult evalQuery(RepositoryConnection conn, String sparqlQuery) { + // TupleQueryResult result = null; + // try { + // + // result = conn.prepareTupleQuery(QueryLanguage.SPARQL, sparqlQuery).evaluate(); + // + // } catch (Exception e) { + // logger.error("error during the execution of the query !", e); + // } + // + // return result; + // } + + /** + * append a sparql update to another + * + * @param appending + * the original sparql update + * @param appended + * the sparql update to be appended + * @return the concatnated sparql update + */ + public static String appendSparqldQuery(String appending, String appended) { + if (appending != null && !appending.isEmpty()) { + if (!appending.endsWith(";")) { + appending = appending.concat(";"); + } + + if (!appending.endsWith("\n")) { + appending = appending.concat("\n"); + } } + appending = appending.concat(appended); + + return appending; + } + + /** + * Create a sparql update ading the triples to the named graph with the + * specified name and send it to the sparql update endpoint specified using + * the given repo connection object. Uses the sesame libraries + * + * @param conn + * sesame repo connection object + * @param triples + * triples to be added to the named graph + * @param graphName + * named graph to which the triples shall be added + */ + // public void processTripleAdditionQuery(RepositoryConnection conn, String triples, String + // graphName) { + // String addTriplesToGraphQuery = SparqlUtil.addTriplesToGraphQuery(graphName, triples); + // SparqlUtil.processQuery_sesame(addTriplesToGraphQuery, conn); + // } + + /** + * Create a triple with the link type as a predicate the src as subject and + * destination as object. This is a conveniece for enabling the creation of + * links in a ageneric way + * + * @param src + * source of the link + * @param dst + * destination of the link + * @param linkType + * type of the link + * @return the rdf triple as n triple + */ + public static String linkTriple(String src, String dst, String linkType) { + StringBuilder sb = new StringBuilder(); + sb.append("<" + src + ">"); + sb.append(" "); + sb.append("<" + linkType + ">"); + sb.append(" "); + sb.append("<" + dst + ">"); + sb.append(" ."); + return sb.toString(); + } } diff --git a/trs/client/trs-client/src/test/java/org/eclipse/lyo/trs/client/TrsClientMigrationTestSuite.java b/trs/client/trs-client/src/test/java/org/eclipse/lyo/trs/client/TrsClientMigrationTestSuite.java new file mode 100644 index 000000000..e953b87b9 --- /dev/null +++ b/trs/client/trs-client/src/test/java/org/eclipse/lyo/trs/client/TrsClientMigrationTestSuite.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License 1.0 + * which is available at http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ + +package org.eclipse.lyo.trs.client; + +import org.eclipse.lyo.trs.client.config.TrsConfigurationTest; +import org.eclipse.lyo.trs.client.handlers.sparql.SparqlBatchingHandlerTest; +import org.junit.jupiter.api.DisplayName; +import org.junit.platform.suite.api.SelectClasses; +import org.junit.platform.suite.api.Suite; + +/** + * A test suite for TRS Client functionality that tests the migration from RDF4J to Lyo Store. + */ +@Suite +@SelectClasses({SparqlBatchingHandlerTest.class, TrsConfigurationTest.class}) +@DisplayName("TRS Client Migration Test Suite") +public class TrsClientMigrationTestSuite {} diff --git a/trs/client/trs-client/src/test/java/org/eclipse/lyo/trs/client/config/TrsConfigurationTest.java b/trs/client/trs-client/src/test/java/org/eclipse/lyo/trs/client/config/TrsConfigurationTest.java new file mode 100644 index 000000000..d0796b61e --- /dev/null +++ b/trs/client/trs-client/src/test/java/org/eclipse/lyo/trs/client/config/TrsConfigurationTest.java @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License 1.0 + * which is available at http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +package org.eclipse.lyo.trs.client.config; + +import static org.junit.jupiter.api.Assertions.*; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.net.URI; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +@DisplayName("TRS Configuration Tests") +public class TrsConfigurationTest { + + @TempDir File tempDir; + + @Test + @DisplayName("Test TrsConfigurationLoader handles missing trs_uri") + public void testTrsConfigurationLoaderMissingTrsUri() throws IOException { + // Create a properties file without trs_uri + File configFile = new File(tempDir, "test.properties"); + try (FileWriter writer = new FileWriter(configFile)) { + writer.write("baseAuth_user=testuser\n"); + writer.write("baseAuth_password=testpass\n"); + } + + // Loading should throw IllegalStateException + IllegalStateException exception = + assertThrows( + IllegalStateException.class, + () -> { + TrsConfigurationLoader.from(configFile); + }); + + assertTrue(exception.getMessage().contains("trs_uri")); + assertTrue(exception.getMessage().contains("missing")); + } + + @Test + @DisplayName("Test TrsConfigurationLoader with valid configuration") + public void testTrsConfigurationLoaderValid() throws IOException { + // Create a valid properties file + File configFile = new File(tempDir, "test.properties"); + try (FileWriter writer = new FileWriter(configFile)) { + writer.write("trs_uri=http://example.org/trs\n"); + writer.write("baseAuth_user=testuser\n"); + writer.write("baseAuth_pwd=testpass\n"); + writer.write("sparql_query_url=http://example.org/sparql/query\n"); + writer.write("sparql_update_url=http://example.org/sparql/update\n"); + writer.write("sparql_username=sparqluser\n"); + writer.write("sparql_password=sparqlpass\n"); + } + + // Loading should succeed + TrsProviderConfiguration config = TrsConfigurationLoader.from(configFile); + + assertNotNull(config); + assertEquals(URI.create("http://example.org/trs"), config.getTrsUri()); + assertEquals("testuser", config.getBasicAuthUsername()); + assertEquals("testpass", config.getBasicAuthPassword()); +// assertEquals("http://example.org/sparql/query", config.getSparqlQueryUrl()); +// assertEquals("http://example.org/sparql/update", config.getSparqlUpdateUrl()); +// assertEquals("sparqluser", config.getSparqlUsername()); +// assertEquals("sparqlpass", config.getSparqlPassword()); + } + + @Test + @DisplayName("Test TrsConfigurationLoader with minimal configuration") + public void testTrsConfigurationLoaderMinimal() throws IOException { + // Create a minimal properties file with only required trs_uri + File configFile = new File(tempDir, "test.properties"); + try (FileWriter writer = new FileWriter(configFile)) { + writer.write("trs_uri=http://example.org/trs\n"); + } + + // Loading should succeed + TrsProviderConfiguration config = TrsConfigurationLoader.from(configFile); + + assertNotNull(config); + assertEquals(URI.create("http://example.org/trs"), config.getTrsUri()); + assertNull(config.getBasicAuthUsername()); + assertNull(config.getBasicAuthPassword()); +// assertNull(config.getSparqlQueryUrl()); +// assertNull(config.getSparqlUpdateUrl()); +// assertNull(config.getSparqlUsername()); +// assertNull(config.getSparqlPassword()); + } + + @Test + @DisplayName("Test TrsConsumerConfiguration with null basic auth") + public void testTrsConsumerConfigurationNullAuth() { + TrsConsumerConfiguration config = + new TrsConsumerConfiguration( + "http://example.org/sparql/query", + "http://example.org/sparql/update", + "sparqluser", + "sparqlpass", + null, // scheduler + null, // basicUsername + null // basicPassword + ); + + // getHttpClient should handle null basic auth gracefully + assertNotNull(config.getHttpClient()); + } + + @Test + @DisplayName("Test TrsConsumerConfiguration with empty basic auth") + public void testTrsConsumerConfigurationEmptyAuth() { + TrsConsumerConfiguration config = + new TrsConsumerConfiguration( + "http://example.org/sparql/query", + "http://example.org/sparql/update", + "sparqluser", + "sparqlpass", + null, // scheduler + "", // empty basicUsername + "" // empty basicPassword + ); + + // getHttpClient should handle empty basic auth gracefully + assertNotNull(config.getHttpClient()); + } + + @Test + @DisplayName("Test TrsConsumerConfiguration with valid basic auth") + public void testTrsConsumerConfigurationValidAuth() { + TrsConsumerConfiguration config = + new TrsConsumerConfiguration( + "http://example.org/sparql/query", + "http://example.org/sparql/update", + "sparqluser", + "sparqlpass", + null, // scheduler + "basicuser", + "basicpass"); + + // getHttpClient should configure basic auth + assertNotNull(config.getHttpClient()); + } +} diff --git a/trs/client/trs-client/src/test/java/org/eclipse/lyo/trs/client/handlers/sparql/SparqlBatchingHandlerTest.java b/trs/client/trs-client/src/test/java/org/eclipse/lyo/trs/client/handlers/sparql/SparqlBatchingHandlerTest.java new file mode 100644 index 000000000..681149848 --- /dev/null +++ b/trs/client/trs-client/src/test/java/org/eclipse/lyo/trs/client/handlers/sparql/SparqlBatchingHandlerTest.java @@ -0,0 +1,207 @@ +/* + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License 1.0 + * which is available at http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +package org.eclipse.lyo.trs.client.handlers.sparql; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.*; + +import java.net.URI; +import java.net.URISyntaxException; +import org.apache.jena.rdf.model.Model; +import org.apache.jena.rdf.model.ModelFactory; +import org.apache.jena.vocabulary.RDF; +import org.eclipse.lyo.core.trs.Creation; +import org.eclipse.lyo.core.trs.Deletion; +import org.eclipse.lyo.core.trs.Modification; +import org.eclipse.lyo.store.Store; +import org.eclipse.lyo.store.StoreAccessException; +import org.eclipse.lyo.store.StoreFactory; +import org.eclipse.lyo.trs.client.model.BaseMember; +import org.eclipse.lyo.trs.client.model.ChangeEventMessageTR; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; + +@DisplayName("SparqlBatchingHandler Tests") +public class SparqlBatchingHandlerTest { + + private SparqlBatchingHandler handler; + private final String sparqlUpdateEndpoint = "http://example.org/sparql/update"; + private final String username = "testuser"; + private final String password = "testpass"; + + @BeforeEach + public void setUp() { + handler = new SparqlBatchingHandler(sparqlUpdateEndpoint, username, password); + } + + @Test + @DisplayName("Test SparqlBatchingHandler uses Lyo Store for finishCycle") + public void testFinishCycleUsesLyoStore() throws StoreAccessException { + // Create mock Store + Store mockStore = mock(Store.class); + + // Mock StoreFactory.sparql method + try (MockedStatic mockedStoreFactory = mockStatic(StoreFactory.class)) { + mockedStoreFactory + .when(() -> StoreFactory.sparql(null, sparqlUpdateEndpoint, username, password)) + .thenReturn(mockStore); + + // Add some test operations to create queries + BaseMember baseMember = createTestBaseMember(); + handler.handleBaseMember(baseMember); + + // Call finishCycle which should use the Store + handler.finishCycle(); + + // Verify that StoreFactory.sparql was called with correct parameters + mockedStoreFactory.verify( + () -> StoreFactory.sparql(null, sparqlUpdateEndpoint, username, password)); + + // Verify that rawUpdateQuery was called on the store + verify(mockStore).rawUpdateQuery(anyString()); + + // Verify that close was called on the store + verify(mockStore).close(); + } + } + + @Test + @DisplayName("Test handleBaseMember creates proper queries") + public void testHandleBaseMember() throws StoreAccessException { + BaseMember baseMember = createTestBaseMember(); + + // This should add queries to the internal list + handler.handleBaseMember(baseMember); + + // We can't directly test the internal queries list, but we can test + // that finishCycle processes them by mocking the Store + Store mockStore = mock(Store.class); + + try (MockedStatic mockedStoreFactory = mockStatic(StoreFactory.class)) { + mockedStoreFactory + .when(() -> StoreFactory.sparql(null, sparqlUpdateEndpoint, username, password)) + .thenReturn(mockStore); + + handler.finishCycle(); + + // Verify rawUpdateQuery was called with some query string + verify(mockStore).rawUpdateQuery(anyString()); + } + } + + @Test + @DisplayName("Test handleChangeEvent with Creation") + public void testHandleChangeEventCreation() throws URISyntaxException, StoreAccessException { + Creation creation = new Creation(); + creation.setChanged(new URI("http://example.org/resource/1")); + creation.setOrder(1); + + Model testModel = ModelFactory.createDefaultModel(); + testModel.add( + testModel.createResource("http://example.org/resource/1"), + RDF.type, + testModel.createResource("http://example.org/TestType")); + + ChangeEventMessageTR eventMessage = new ChangeEventMessageTR(creation, testModel); + + handler.handleChangeEvent(eventMessage); + + // Verify by testing finishCycle + Store mockStore = mock(Store.class); + + try (MockedStatic mockedStoreFactory = mockStatic(StoreFactory.class)) { + mockedStoreFactory + .when(() -> StoreFactory.sparql(null, sparqlUpdateEndpoint, username, password)) + .thenReturn(mockStore); + + handler.finishCycle(); + + verify(mockStore).rawUpdateQuery(anyString()); + } + } + + @Test + @DisplayName("Test handleChangeEvent with Deletion") + public void testHandleChangeEventDeletion() throws URISyntaxException, StoreAccessException { + Deletion deletion = new Deletion(); + deletion.setChanged(new URI("http://example.org/resource/1")); + deletion.setOrder(2); + + ChangeEventMessageTR eventMessage = new ChangeEventMessageTR(deletion, null); + + handler.handleChangeEvent(eventMessage); + + // Verify by testing finishCycle + Store mockStore = mock(Store.class); + + try (MockedStatic mockedStoreFactory = mockStatic(StoreFactory.class)) { + mockedStoreFactory + .when(() -> StoreFactory.sparql(null, sparqlUpdateEndpoint, username, password)) + .thenReturn(mockStore); + + handler.finishCycle(); + + verify(mockStore).rawUpdateQuery(anyString()); + } + } + + @Test + @DisplayName("Test handleChangeEvent with Modification") + public void testHandleChangeEventModification() throws URISyntaxException, StoreAccessException { + Modification modification = new Modification(); + modification.setChanged(new URI("http://example.org/resource/1")); + modification.setOrder(3); + + Model testModel = ModelFactory.createDefaultModel(); + testModel.add( + testModel.createResource("http://example.org/resource/1"), + RDF.type, + testModel.createResource("http://example.org/ModifiedType")); + + ChangeEventMessageTR eventMessage = new ChangeEventMessageTR(modification, testModel); + + handler.handleChangeEvent(eventMessage); + + // Verify by testing finishCycle + Store mockStore = mock(Store.class); + + try (MockedStatic mockedStoreFactory = mockStatic(StoreFactory.class)) { + mockedStoreFactory + .when(() -> StoreFactory.sparql(null, sparqlUpdateEndpoint, username, password)) + .thenReturn(mockStore); + + handler.finishCycle(); + + verify(mockStore).rawUpdateQuery(anyString()); + } + } + + private BaseMember createTestBaseMember() { + try { + URI memberUri = new URI("http://example.org/basemember/1"); + Model model = ModelFactory.createDefaultModel(); + model.add( + model.createResource(memberUri.toString()), + RDF.type, + model.createResource("http://example.org/TestResource")); + + return new BaseMember(memberUri, model); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } + } +}