diff --git a/CHANGELOG.md b/CHANGELOG.md index 479105f59..4e41deeaf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # JOPA - Change Log +### 2.11.0 - 2026-07-13 + +- Add support for `countDistinct` in Criteria API, properly implement COUNT DISTINCT in SOQL (Enhancement #448). +- Add support for ASK queries in SOQL/Criteria API (Enhancement #451). +- Add support for custom identifier generators (Enhancement #373). + ### 2.10.1 - 2026-06-30 - Allow ordering by entity identifier in SOQL (Bug #439). diff --git a/datatype/pom.xml b/datatype/pom.xml index c729a0529..8ac2a12ff 100644 --- a/datatype/pom.xml +++ b/datatype/pom.xml @@ -6,7 +6,7 @@ jopa-all cz.cvut.kbss.jopa - 2.10.1 + 2.11.0 ../pom.xml diff --git a/jopa-api/pom.xml b/jopa-api/pom.xml index 26e12414a..5dbf1efbf 100644 --- a/jopa-api/pom.xml +++ b/jopa-api/pom.xml @@ -6,7 +6,7 @@ cz.cvut.kbss.jopa jopa-all - 2.10.1 + 2.11.0 ../pom.xml diff --git a/jopa-api/src/main/java/cz/cvut/kbss/jopa/id/IdentifierGenerator.java b/jopa-api/src/main/java/cz/cvut/kbss/jopa/id/IdentifierGenerator.java new file mode 100644 index 000000000..5e3db2929 --- /dev/null +++ b/jopa-api/src/main/java/cz/cvut/kbss/jopa/id/IdentifierGenerator.java @@ -0,0 +1,20 @@ +package cz.cvut.kbss.jopa.id; + +import cz.cvut.kbss.jopa.model.metamodel.EntityType; +import cz.cvut.kbss.ontodriver.Connection; + +/** + * Generates identifiers for entities. + */ +public interface IdentifierGenerator { + + /** + * Generate identifier for the specified entity. + * + * @param entity Entity for which to generate the identifier + * @param entityClass Entity class + * @param connection Storage connection + * @return Generated identifier + */ + Object generate(Object entity, EntityType entityClass, Connection connection); +} diff --git a/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/annotations/IdGenerator.java b/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/annotations/IdGenerator.java new file mode 100644 index 000000000..fc423cb71 --- /dev/null +++ b/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/annotations/IdGenerator.java @@ -0,0 +1,23 @@ +package cz.cvut.kbss.jopa.model.annotations; + +import cz.cvut.kbss.jopa.id.IdentifierGenerator; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Specifies the identifier generator to use for the entity. + */ +@Documented +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +public @interface IdGenerator { + + /** + * The identifier generator class. + */ + Class value(); +} diff --git a/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/query/criteria/CriteriaBuilder.java b/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/query/criteria/CriteriaBuilder.java index 55e6cf8ef..10e832b84 100644 --- a/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/query/criteria/CriteriaBuilder.java +++ b/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/query/criteria/CriteriaBuilder.java @@ -57,14 +57,23 @@ public interface CriteriaBuilder extends PredicateFactory { Expression floor(Expression x); /** - * Create an aggregate expression applying the count operation. Return type of count function in SPARQL is - * xsd:integer which JOPA internally represents as Integer. + * Create an aggregate expression applying the count operation. * * @param x expression representing input value to count operation * @return count expression + * @implNote Return type of count function in SPARQL is xsd:integer which JOPA internally represents as Integer. */ Expression count(Expression x); + /** + * Create an aggregate expression applying the count distinct operation. + * + * @param x expression representing input value to count operation + * @return count distinct expression + * @implNote Return type of count function in SPARQL is xsd:integer which JOPA internally represents as Integer. + */ + Expression countDistinct(Expression x); + /** * Create expression to return length of a string. * diff --git a/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/query/criteria/CriteriaQuery.java b/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/query/criteria/CriteriaQuery.java index 667288b7d..648538646 100644 --- a/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/query/criteria/CriteriaQuery.java +++ b/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/query/criteria/CriteriaQuery.java @@ -53,6 +53,13 @@ public interface CriteriaQuery { */ CriteriaQuery select(Selection selection); + /** + * Specify that the query is an ask query. + * + * @return the modified query + */ + CriteriaQuery ask(); + /** * Modify the query to restrict the query result according to the specified boolean expression. Replaces the * previously added restriction(s), if any. diff --git a/jopa-distribution/pom.xml b/jopa-distribution/pom.xml index e1294828b..b88a2e7a5 100644 --- a/jopa-distribution/pom.xml +++ b/jopa-distribution/pom.xml @@ -6,7 +6,7 @@ cz.cvut.kbss.jopa jopa-all - 2.10.1 + 2.11.0 ../pom.xml diff --git a/jopa-impl/pom.xml b/jopa-impl/pom.xml index 4912214e2..197a08d3d 100644 --- a/jopa-impl/pom.xml +++ b/jopa-impl/pom.xml @@ -6,7 +6,7 @@ cz.cvut.kbss.jopa jopa-all - 2.10.1 + 2.11.0 ../pom.xml diff --git a/jopa-impl/src/main/antlr4/cz/cvut/kbss/jopa/query/soql/Soql.g4 b/jopa-impl/src/main/antlr4/cz/cvut/kbss/jopa/query/soql/Soql.g4 index 8091af490..9cdb4d8bb 100644 --- a/jopa-impl/src/main/antlr4/cz/cvut/kbss/jopa/query/soql/Soql.g4 +++ b/jopa-impl/src/main/antlr4/cz/cvut/kbss/jopa/query/soql/Soql.g4 @@ -2,10 +2,12 @@ grammar Soql; start: querySentence EOF ; -querySentence: selectStatement ; +querySentence: selectStatement | askStatement ; selectStatement: selectClause fromClause whereClause? groupByClause? orderByClause? ; +askStatement: ASK fromClause whereClause? ; + singleValuedObjectPathExpression: simpleSubpath DOT singleValuedObjectField ; simpleSubpath: singleValuedObjectField (DOT simpleSubpath)* ; @@ -152,6 +154,8 @@ comparisonOperator SELECT: 'SELECT' ; +ASK: 'ASK' ; + WHERE: 'WHERE' ; NOT: 'NOT' ; diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/id/AbstractIdentifierGenerator.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/id/AbstractIdentifierGenerator.java new file mode 100644 index 000000000..19faa6885 --- /dev/null +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/id/AbstractIdentifierGenerator.java @@ -0,0 +1,39 @@ +package cz.cvut.kbss.jopa.id; + +import cz.cvut.kbss.ontodriver.Connection; +import cz.cvut.kbss.ontodriver.exception.IdentifierGenerationException; +import cz.cvut.kbss.ontodriver.exception.OntoDriverException; +import cz.cvut.kbss.ontodriver.model.Assertion; +import cz.cvut.kbss.ontodriver.model.AxiomImpl; +import cz.cvut.kbss.ontodriver.model.NamedResource; +import cz.cvut.kbss.ontodriver.model.Value; + +import java.net.URI; +import java.util.Set; + +/** + * Common superclass with utilities for identifier generation. + */ +public abstract class AbstractIdentifierGenerator implements IdentifierGenerator { + + /** + * Checks if a class assertion for the specified identifier exists in the repository. + *

+ * This check is done against the default context. + * + * @param identifier Instance identifier + * @param classIri Class identifier + * @param connection Repository connection + * @return {@code true} if the class assertion exists, {@code false} otherwise + * @throws IdentifierGenerationException If unable to check if the identifier exists + */ + protected boolean exists(URI identifier, URI classIri, Connection connection) { + try { + return connection.contains(new AxiomImpl<>(NamedResource.create(identifier), + Assertion.createClassAssertion(false), + new Value<>(classIri)), Set.of()); + } catch (OntoDriverException e) { + throw new IdentifierGenerationException("Unable to check if identifier '" + identifier + "' exists.", e); + } + } +} diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/id/RandomNumberIdentifierGenerator.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/id/RandomNumberIdentifierGenerator.java new file mode 100644 index 000000000..5f621428d --- /dev/null +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/id/RandomNumberIdentifierGenerator.java @@ -0,0 +1,46 @@ +package cz.cvut.kbss.jopa.id; + +import cz.cvut.kbss.jopa.model.metamodel.EntityType; +import cz.cvut.kbss.ontodriver.Connection; +import cz.cvut.kbss.ontodriver.exception.IdentifierGenerationException; + +import java.net.URI; +import java.util.Objects; +import java.util.Random; + +public class RandomNumberIdentifierGenerator extends AbstractIdentifierGenerator { + + private static final int GENERATION_THRESHOLD = 64; + + private static final Random RANDOM = new Random(); + + @Override + public URI generate(Object entity, EntityType entityClass, Connection connection) { + Objects.requireNonNull(entity); + Objects.requireNonNull(entityClass); + Objects.requireNonNull(connection); + + final URI classUri = entityClass.getIRI().toURI(); + int counter = 0; + while (counter++ < GENERATION_THRESHOLD) { + final URI id = generate(classUri); + if (!exists(id, classUri, connection)) { + return id; + } + } + throw new IdentifierGenerationException("Unable to generate a unique identifier."); + } + + private URI generate(URI classUri) { + if (classUri.getFragment() != null) { + return URI.create(classUri + "_instance" + RANDOM.nextInt()); + } else { + String base = classUri.toString(); + if (base.endsWith("/")) { + return URI.create(base + "instance" + RANDOM.nextInt()); + } else { + return URI.create(base + "/instance" + RANDOM.nextInt()); + } + } + } +} diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/CriteriaQueryImpl.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/CriteriaQueryImpl.java index 12ac6ba82..4467deeac 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/CriteriaQueryImpl.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/CriteriaQueryImpl.java @@ -25,6 +25,7 @@ import cz.cvut.kbss.jopa.model.query.criteria.Predicate; import cz.cvut.kbss.jopa.model.query.criteria.Root; import cz.cvut.kbss.jopa.model.query.criteria.Selection; +import cz.cvut.kbss.jopa.query.QueryType; import cz.cvut.kbss.jopa.query.criteria.AbstractPredicate; import cz.cvut.kbss.jopa.query.criteria.CriteriaBuilderImpl; import cz.cvut.kbss.jopa.query.criteria.CriteriaParameterFiller; @@ -45,6 +46,8 @@ public class CriteriaQueryImpl implements CriteriaQuery { private final Metamodel metamodel; private final CriteriaBuilderImpl cb; + private QueryType queryType = QueryType.SELECT; + public CriteriaQueryImpl(CriteriaQueryHolder query, Metamodel metamodel, CriteriaBuilderImpl cb) { this.query = Objects.requireNonNull(query); @@ -61,7 +64,7 @@ public Root from(Class entityClass) { @Override public Root from(EntityType entity) { - RootImpl root = new RootImpl<>(metamodel, null, entity.getBindableJavaType(), this.cb); + RootImpl root = new RootImpl<>(metamodel, null, entity.getJavaType(), this.cb); query.setRoot(root); return root; } @@ -69,9 +72,18 @@ public Root from(EntityType entity) { @Override public CriteriaQuery select(Selection selection) { query.setSelection(selection); + this.queryType = QueryType.SELECT; return this; } + @Override + public CriteriaQuery ask() { + this.queryType = QueryType.ASK; + query.setSelection(null); + assert Boolean.class.equals(query.getResultType()); + return (CriteriaQuery) this; + } + @Override public CriteriaQuery where(Expression expression) { query.setWhere(cb.wrapExpressionToPredicateWithRepair(expression)); @@ -188,44 +200,69 @@ public CriteriaQuery having(Predicate... restrictions) { * @return string representation of SOQL query */ public String translateQuery(CriteriaParameterFiller parameterFiller) { - StringBuilder stringBuilder = new StringBuilder(); - stringBuilder.append(SoqlConstants.SELECT).append(' '); + StringBuilder soqlQuery = new StringBuilder(); + soqlQuery.append(queryType.getKeyword()); if (isDistinct()) { - stringBuilder.append(SoqlConstants.DISTINCT).append(' '); + soqlQuery.append(' ').append(SoqlConstants.DISTINCT); } - ((AbstractExpression) query.getSelection()).setExpressionToQuery(stringBuilder, parameterFiller); + if (queryType == QueryType.SELECT) { + soqlQuery.append(' '); + ((AbstractExpression) query.getSelection()).setExpressionToQuery(soqlQuery, parameterFiller); + } + + appendFromClause(parameterFiller, soqlQuery); + + appendWhereClause(parameterFiller, soqlQuery); + + appendGroupByClause(parameterFiller, soqlQuery); - stringBuilder.append(' ').append(SoqlConstants.FROM).append(' ').append(((RootImpl) query.getRoot()).getJavaType().getSimpleName()).append(' '); - ((RootImpl) query.getRoot()).setExpressionToQuery(stringBuilder, parameterFiller); + appendHavingClause(parameterFiller, soqlQuery); + appendOrderByClause(parameterFiller, soqlQuery); + + return soqlQuery.toString(); + } + + private void appendFromClause(CriteriaParameterFiller parameterFiller, StringBuilder soqlQuery) { + soqlQuery.append(' ').append(SoqlConstants.FROM).append(' ') + .append(((RootImpl) query.getRoot()).getJavaType().getSimpleName()).append(' '); + ((RootImpl) query.getRoot()).setExpressionToQuery(soqlQuery, parameterFiller); + } + + private void appendWhereClause(CriteriaParameterFiller parameterFiller, StringBuilder soqlQuery) { if (query.getWhere() != null && !query.getWhere().getExpressions().isEmpty()) { - stringBuilder.append(' ').append(SoqlConstants.WHERE).append(' '); - ((AbstractPredicate) query.getWhere()).setExpressionToQuery(stringBuilder, parameterFiller); + soqlQuery.append(' ').append(SoqlConstants.WHERE).append(' '); + ((AbstractPredicate) query.getWhere()).setExpressionToQuery(soqlQuery, parameterFiller); } + } + private void appendGroupByClause(CriteriaParameterFiller parameterFiller, StringBuilder soqlQuery) { if (query.getGroupBy() != null && !query.getGroupBy().isEmpty()) { - stringBuilder.append(' ').append(SoqlConstants.GROUP_BY).append(' '); + soqlQuery.append(' ').append(SoqlConstants.GROUP_BY).append(' '); for (Expression groupBy : query.getGroupBy()) { - ((AbstractExpression) groupBy).setExpressionToQuery(stringBuilder, parameterFiller); + ((AbstractExpression) groupBy).setExpressionToQuery(soqlQuery, parameterFiller); } } + } + private void appendHavingClause(CriteriaParameterFiller parameterFiller, StringBuilder soqlQuery) { if (query.getHaving() != null && !query.getHaving().getExpressions().isEmpty()) { - stringBuilder.append(" HAVING "); - ((AbstractPredicate) query.getHaving()).setExpressionToQuery(stringBuilder, parameterFiller); + soqlQuery.append(" HAVING "); + ((AbstractPredicate) query.getHaving()).setExpressionToQuery(soqlQuery, parameterFiller); } + } + private void appendOrderByClause(CriteriaParameterFiller parameterFiller, StringBuilder soqlQuery) { if (!getOrderList().isEmpty()) { - stringBuilder.append(' ').append(SoqlConstants.ORDER_BY).append(' '); + soqlQuery.append(' ').append(SoqlConstants.ORDER_BY).append(' '); List orders = getOrderList(); for (int i = 0; i < orders.size(); i++) { - ((AbstractExpression) orders.get(i).getExpression()).setExpressionToQuery(stringBuilder, parameterFiller); - stringBuilder.append(' ').append(orders.get(i).isAscending() ? SoqlConstants.ASC : SoqlConstants.DESC); + ((AbstractExpression) orders.get(i).getExpression()).setExpressionToQuery(soqlQuery, parameterFiller); + soqlQuery.append(' ').append(orders.get(i).isAscending() ? SoqlConstants.ASC : SoqlConstants.DESC); if (orders.size() > 1 && (i + 1) != orders.size()) { - stringBuilder.append(", "); + soqlQuery.append(", "); } } } - return stringBuilder.toString(); } } diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/MetamodelImpl.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/MetamodelImpl.java index b39986af0..44a9f8018 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/MetamodelImpl.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/MetamodelImpl.java @@ -18,6 +18,8 @@ package cz.cvut.kbss.jopa.model; import cz.cvut.kbss.jopa.exceptions.OWLPersistenceException; +import cz.cvut.kbss.jopa.id.IdentifierGenerator; +import cz.cvut.kbss.jopa.id.RandomNumberIdentifierGenerator; import cz.cvut.kbss.jopa.loaders.PersistenceUnitClassFinder; import cz.cvut.kbss.jopa.model.metamodel.EntityType; import cz.cvut.kbss.jopa.model.metamodel.IdentifiableEntityType; @@ -61,6 +63,8 @@ public class MetamodelImpl implements Metamodel, MetamodelProvider, MetamodelCla // Proxy classes for results of EntityManager.getReference private final Map, Class> referenceProxyClasses = new ConcurrentHashMap<>(); + private Map, IdentifierGenerator> idGenerators; + private NamedQueryManager namedQueryManager; private ResultSetMappingManager resultSetMappingManager; private NamedEntityGraphManager namedEntityGraphManager; @@ -108,6 +112,7 @@ private void initFromMetamodelBuilder(MetamodelBuilder metamodelBuilder) { this.namespaceResolver = metamodelBuilder.getNamespaceResolver(); this.typeReferenceMap = metamodelBuilder.getTypeReferenceMap(); this.lazyLoadingProxyClasses.putAll(metamodelBuilder.getLazyLoadingEntityProxyClasses()); + this.idGenerators = metamodelBuilder.getIdGenerators(); } /** @@ -176,6 +181,10 @@ public NamedEntityGraphManager getNamedEntityGraphManager() { return namedEntityGraphManager; } + public IdentifierGenerator getIdentifierGenerator(EntityType entityType) { + return idGenerators.getOrDefault(entityType, new RandomNumberIdentifierGenerator()); + } + @Override public Set getModuleExtractionExtraSignature() { return Collections.unmodifiableSet(getSignatureInternal()); diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/MetamodelBuilder.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/MetamodelBuilder.java index 4d751cbb5..733371e3e 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/MetamodelBuilder.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/MetamodelBuilder.java @@ -18,11 +18,13 @@ package cz.cvut.kbss.jopa.model.metamodel; import cz.cvut.kbss.jopa.exception.MetamodelInitializationException; +import cz.cvut.kbss.jopa.id.IdentifierGenerator; import cz.cvut.kbss.jopa.loaders.PersistenceUnitClassFinder; import cz.cvut.kbss.jopa.model.JOPAPersistenceProperties; import cz.cvut.kbss.jopa.model.NamedEntityGraphManager; import cz.cvut.kbss.jopa.model.NamedEntityGraphProcessor; import cz.cvut.kbss.jopa.model.TypeReferenceMap; +import cz.cvut.kbss.jopa.model.annotations.IdGenerator; import cz.cvut.kbss.jopa.model.annotations.Inheritance; import cz.cvut.kbss.jopa.model.annotations.InheritanceType; import cz.cvut.kbss.jopa.model.annotations.OWLAnnotationProperty; @@ -38,7 +40,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.lang.reflect.Constructor; import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; @@ -333,6 +337,32 @@ public Map, Class> getLazyLoadingEntityProxyClasses() { return Collections.unmodifiableMap(lazyLoadingProxyClasses); } + public Map, IdentifierGenerator> getIdGenerators() { + final Map, IdentifierGenerator> generators = new HashMap<>(); + final Map, IdentifierGenerator> result = new HashMap<>(); + for (EntityType et : getEntities().values()) { + if (et.getJavaType().isInterface() || !et.getIdentifier().isGenerated()) { + continue; + } + final IdGenerator idGeneratorAnn = et.getIdentifier().getJavaField().getAnnotation(IdGenerator.class); + if (idGeneratorAnn == null) { + continue; + } + final IdentifierGenerator gen = generators.computeIfAbsent(idGeneratorAnn.value(), cls -> { + try { + final Constructor constructor = cls.getDeclaredConstructor(); + return constructor.newInstance(); + } catch (NoSuchMethodException | SecurityException e) { + throw new MetamodelInitializationException("Custom identifier generator class " + cls + " is missing public no-arg constructor."); + } catch (InvocationTargetException | InstantiationException | IllegalAccessException e) { + throw new MetamodelInitializationException("Failed to create instance of custom identifier generator " + cls, e); + } + }); + result.put(et, gen); + } + return result; + } + public Set getAnnotatedAccessorsForClass(IdentifiableType k) { return annotatedAccessors.getOrDefault(k, Collections.emptySet()); } diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/StaticMetamodelInitializer.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/StaticMetamodelInitializer.java index 76f6dc08a..fcc869eaa 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/StaticMetamodelInitializer.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/metamodel/StaticMetamodelInitializer.java @@ -179,7 +179,7 @@ private static void setPropertyIriFieldValue(ManagedType et, Field f) thr } } - private FieldSpecification getMetamodelMember(Field field, ManagedType type) { + private static FieldSpecification getMetamodelMember(Field field, ManagedType type) { LOG.trace("Finding metamodel member for static metamodel field {}.", field); return getDeclaredIdentifier(field, type) .orElseGet(() -> getDeclaredAttribute(field, type) diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/EntityMappingHelper.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/EntityMappingHelper.java index 05817b7b8..7422dfceb 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/EntityMappingHelper.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/EntityMappingHelper.java @@ -35,7 +35,7 @@ interface EntityMappingHelper extends ConfigurationHolder { EntityType getEntityType(Class cls); - URI generateIdentifier(EntityType et); + Object generateIdentifier(Object entity, EntityType et); T getOriginalInstance(T clone); diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/FieldStrategy.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/FieldStrategy.java index 4d8e6826e..c7b56a2c2 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/FieldStrategy.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/FieldStrategy.java @@ -224,12 +224,12 @@ Object extractFieldValueFromInstance(Object instance) { } URI resolveValueIdentifier(E instance, EntityType valEt) { - URI id = EntityPropertiesUtils.getIdentifier(instance, valEt); + Object id = EntityPropertiesUtils.getIdentifier(instance, valEt); if (id == null) { - id = mapper.generateIdentifier(valEt); + id = mapper.generateIdentifier(instance, valEt); EntityPropertiesUtils.setIdentifier(id, instance, valEt); } - return id; + return id instanceof URI uri ? uri : URI.create(id.toString()); } /** diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/ObjectOntologyMapper.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/ObjectOntologyMapper.java index f0851cf31..7b69bdf88 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/ObjectOntologyMapper.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/ObjectOntologyMapper.java @@ -18,7 +18,6 @@ package cz.cvut.kbss.jopa.oom; import cz.cvut.kbss.jopa.model.descriptors.Descriptor; -import cz.cvut.kbss.jopa.model.metamodel.EntityType; import cz.cvut.kbss.jopa.model.metamodel.FieldSpecification; import cz.cvut.kbss.jopa.oom.exception.UnpersistedChangeException; import cz.cvut.kbss.jopa.sessions.util.AxiomBasedLoadingParameters; @@ -81,14 +80,6 @@ public interface ObjectOntologyMapper { */ void loadFieldValue(T entity, FieldSpecification fieldSpec, Descriptor descriptor); - /** - * Generates a fresh identifier for an instance of the specified entity type. - * - * @param et Entity type used as base for the identifier - * @return New entity identifier - */ - URI generateIdentifier(EntityType et); - /** * Persists the specified entity into the underlying ontology. * diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/ObjectOntologyMapperImpl.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/ObjectOntologyMapperImpl.java index dbefe25c0..5c79e73cb 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/ObjectOntologyMapperImpl.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/oom/ObjectOntologyMapperImpl.java @@ -252,12 +252,8 @@ public void persistEntity(URI identifier, T entity, Descriptor descriptor) { } @Override - public URI generateIdentifier(EntityType et) { - try { - return storageConnection.generateIdentifier(et.getIRI().toURI()); - } catch (OntoDriverException e) { - throw new StorageAccessException(e); - } + public Object generateIdentifier(Object entity, EntityType et) { + return uow.getMetamodel().getIdentifierGenerator(et).generate(entity, et, storageConnection); } private void persistPendingReferences(T instance, NamedResource identifier) { @@ -293,23 +289,25 @@ public T getEntityFromCacheOrOntology(Class cls, URI identifier, ObjectGr if (orig != null) { return orig; } - return defaultInstanceLoader.loadCached(getEntityType(cls), identifier, objectGraphInfo.descriptor()).orElseGet(() -> { - if (instanceRegistry.containsKey(identifier)) { - final Object existing = instanceRegistry.get(identifier); - if (!cls.isAssignableFrom(existing.getClass())) { - throw individualAlreadyManaged(identifier); - } - // This prevents endless cycles in bidirectional relationships - return cls.cast(existing); - } else { - // setup loading params - LoadingParameters params = initEntityLoadingParameters(cls, identifier, objectGraphInfo); - return axiomsPerEntity.containsKey(identifier) ? loadEntityFromAxioms(params) : loadEntityInternal(params); - } - }); - } - - private LoadingParameters initEntityLoadingParameters(Class cls, URI identifier, ObjectGraphInfo objectGraphInfo) { + return defaultInstanceLoader.loadCached(getEntityType(cls), identifier, objectGraphInfo.descriptor()) + .orElseGet(() -> { + if (instanceRegistry.containsKey(identifier)) { + final Object existing = instanceRegistry.get(identifier); + if (!cls.isAssignableFrom(existing.getClass())) { + throw individualAlreadyManaged(identifier); + } + // This prevents endless cycles in bidirectional relationships + return cls.cast(existing); + } else { + // setup loading params + LoadingParameters params = initEntityLoadingParameters(cls, identifier, objectGraphInfo); + return axiomsPerEntity.containsKey(identifier) ? loadEntityFromAxioms(params) : loadEntityInternal(params); + } + }); + } + + private LoadingParameters initEntityLoadingParameters(Class cls, URI identifier, + ObjectGraphInfo objectGraphInfo) { return new LoadingParameters<>(cls, identifier, objectGraphInfo.descriptor(), objectGraphInfo.fetchGraph(), false, uow.isReadOnly()); } diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/QueryType.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/QueryType.java index c598766fa..f43acf4c5 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/QueryType.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/QueryType.java @@ -17,12 +17,14 @@ */ package cz.cvut.kbss.jopa.query; +import cz.cvut.kbss.jopa.query.soql.SoqlConstants; + /** * Types of queries. */ public enum QueryType { - SELECT("SELECT"), - ASK("ASK"), + SELECT(SoqlConstants.SELECT), + ASK(SoqlConstants.ASK), DESCRIBE("DESCRIBE"), DELETE("DELETE"), INSERT("INSERT"), diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/CriteriaBuilderImpl.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/CriteriaBuilderImpl.java index a6139c56f..c2ac1b38c 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/CriteriaBuilderImpl.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/CriteriaBuilderImpl.java @@ -29,6 +29,7 @@ import cz.cvut.kbss.jopa.query.criteria.expressions.AbstractExpression; import cz.cvut.kbss.jopa.query.criteria.expressions.AbstractPathExpression; import cz.cvut.kbss.jopa.query.criteria.expressions.CeilFunction; +import cz.cvut.kbss.jopa.query.criteria.expressions.CountDistinctFunction; import cz.cvut.kbss.jopa.query.criteria.expressions.CountFunction; import cz.cvut.kbss.jopa.query.criteria.expressions.ExpressionEqualImpl; import cz.cvut.kbss.jopa.query.criteria.expressions.ExpressionGreaterThanImpl; @@ -84,6 +85,12 @@ public Expression count(Expression x) { return new CountFunction((AbstractPathExpression) x, this); } + @Override + public Expression countDistinct(Expression x) { + validateFunctionArgument(x); + return new CountDistinctFunction((AbstractPathExpression) x, this); + } + @Override public Expression ceil(Expression x) { validateFunctionArgument(x); diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/AbstractFunctionExpression.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/AbstractFunctionExpression.java index 46d49fcf7..03f9b279f 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/AbstractFunctionExpression.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/AbstractFunctionExpression.java @@ -34,13 +34,17 @@ public AbstractFunctionExpression(Class type, CriteriaBuilder cb, AbstractPat @Override public void setExpressionToQuery(StringBuilder query, CriteriaParameterFiller parameterFiller) { query.append(getFunctionName()).append("("); + constructFunctionArguments(query, parameterFiller); + query.append(")"); + } + + protected void constructFunctionArguments(StringBuilder query, CriteriaParameterFiller parameterFiller) { for (int i = 0; i < argumentExpression.size(); i++) { argumentExpression.get(i).setExpressionToQuery(query, parameterFiller); if (i < argumentExpression.size() - 1) { query.append(','); } } - query.append(")"); } public abstract String getFunctionName(); diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/CountDistinctFunction.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/CountDistinctFunction.java new file mode 100644 index 000000000..559f02a69 --- /dev/null +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/CountDistinctFunction.java @@ -0,0 +1,17 @@ +package cz.cvut.kbss.jopa.query.criteria.expressions; + +import cz.cvut.kbss.jopa.model.query.criteria.CriteriaBuilder; +import cz.cvut.kbss.jopa.query.criteria.CriteriaParameterFiller; + +public class CountDistinctFunction extends CountFunction { + + public CountDistinctFunction(AbstractPathExpression expression, CriteriaBuilder cb) { + super(expression, cb); + } + + @Override + protected void constructFunctionArguments(StringBuilder query, CriteriaParameterFiller parameterFiller) { + query.append("DISTINCT "); + super.constructFunctionArguments(query, parameterFiller); + } +} diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/soql/AggregateFunction.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/soql/AggregateFunction.java new file mode 100644 index 000000000..6a3d349f4 --- /dev/null +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/soql/AggregateFunction.java @@ -0,0 +1,42 @@ +/* + * JOPA + * Copyright (C) 2026 Czech Technical University in Prague + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3.0 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. + */ +package cz.cvut.kbss.jopa.query.soql; + +/** + * Aggregate functions supported in SOQL select expressions. + */ +enum AggregateFunction { + + COUNT(SoqlConstants.Functions.COUNT, "?count"); + + private final String soqlName; + private final String resultVariable; + + AggregateFunction(String soqlName, String resultVariable) { + this.soqlName = soqlName; + this.resultVariable = resultVariable; + } + + String soqlName() { + return soqlName; + } + + String resultVariable() { + return resultVariable; + } +} diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/soql/SelectProjection.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/soql/SelectProjection.java new file mode 100644 index 000000000..4a041be28 --- /dev/null +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/soql/SelectProjection.java @@ -0,0 +1,24 @@ +package cz.cvut.kbss.jopa.query.soql; + +/** + * Captures the kind of projection in the SOQL SELECT clause. + * + * @param aggregateFunction the aggregate function wrapping the projected variable, or {@code null} for a plain + * projection + * @param distinct whether the outer {@code SELECT DISTINCT} modifier is present + * @param aggregateDistinct whether {@code DISTINCT} is present inside the aggregate argument + */ +record SelectProjection(AggregateFunction aggregateFunction, boolean distinct, boolean aggregateDistinct) { + + SelectProjection withAggregateFunction(AggregateFunction fn) { + return new SelectProjection(fn, distinct, aggregateDistinct); + } + + SelectProjection withDistinct(boolean value) { + return new SelectProjection(aggregateFunction, value, aggregateDistinct); + } + + SelectProjection withAggregateDistinct(boolean value) { + return new SelectProjection(aggregateFunction, distinct, value); + } +} diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/soql/SoqlConstants.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/soql/SoqlConstants.java index ab65a410c..aa377a149 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/soql/SoqlConstants.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/soql/SoqlConstants.java @@ -27,6 +27,11 @@ public class SoqlConstants { */ public static final String SELECT = "SELECT"; + /** + * {@code ASK} keyword. + */ + public static final String ASK = "ASK"; + /** * {@code DISTINCT} operator. */ diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/soql/SoqlQueryListener.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/soql/SoqlQueryListener.java index f7a5dd759..5e26f35a6 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/soql/SoqlQueryListener.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/soql/SoqlQueryListener.java @@ -57,7 +57,7 @@ public class SoqlQueryListener extends SoqlBaseListener { private String soql; private String sparql; - private String typeDef = SoqlConstants.SELECT; + private QueryType typeDef = QueryType.SELECT; // keeps pointer at created object of SoqlAttribute while processing other necessary rules private SoqlAttribute attrPointer; @@ -78,9 +78,7 @@ public class SoqlQueryListener extends SoqlBaseListener { private final Map, TriplePatternEnhancer> tpEnhancers = new HashMap<>(); - private boolean isSelectedParamDistinct = false; - - private boolean isSelectedParamCount = false; + private SelectProjection selectProjection = new SelectProjection(null, false, false); private boolean isInObjectIdentifierExpression = false; @@ -153,11 +151,14 @@ private SoqlNode linkSimpleSubpath(ParserRuleContext ctx) { @Override public void enterSelectClause(SoqlParser.SelectClauseContext ctx) { - this.typeDef = QueryType.SELECT.getKeyword(); + this.typeDef = QueryType.SELECT; - if (ctx.DISTINCT() != null) { - this.isSelectedParamDistinct = true; - } + this.selectProjection = selectProjection.withDistinct(ctx.DISTINCT() != null); + } + + @Override + public void enterAskStatement(SoqlParser.AskStatementContext ctx) { + this.typeDef = QueryType.ASK; } private void pushNewAttribute(SoqlAttribute myAttr) { @@ -175,7 +176,7 @@ private void popAttribute() { @Override public void exitSelectExpression(SoqlParser.SelectExpressionContext ctx) { - if (!isSelectedParamCount) { + if (selectProjection.aggregateFunction() == null) { this.projectedVariable = ctx.getText(); } } @@ -183,10 +184,8 @@ public void exitSelectExpression(SoqlParser.SelectExpressionContext ctx) { @Override public void enterAggregateExpression(SoqlParser.AggregateExpressionContext ctx) { if (ctx.COUNT() != null) { - isSelectedParamCount = true; - if (ctx.DISTINCT() != null) { - isSelectedParamDistinct = true; - } + selectProjection = selectProjection.withAggregateFunction(AggregateFunction.COUNT) + .withAggregateDistinct(ctx.DISTINCT() != null); if (ctx.simpleSubpath() != null) { this.projectedVariable = ctx.simpleSubpath().getText(); @@ -546,19 +545,11 @@ private void buildSparqlQueryString() { return; } - // the first attribute is either a projected parameter or a type attribute - String selectParameter = getSelectParameter(attributes.get(0)); - - StringBuilder newQueryBuilder = new StringBuilder(typeDef); - if (isSelectedParamCount) { - newQueryBuilder.append(getCountPart(selectParameter)); - } else { - if (isSelectedParamDistinct) { - newQueryBuilder.append(' ').append(SoqlConstants.DISTINCT); - } - newQueryBuilder.append(' ').append(selectParameter).append(' '); + StringBuilder newQueryBuilder = new StringBuilder(typeDef.getKeyword()); + if (typeDef == QueryType.SELECT) { + appendProjection(newQueryBuilder); } - newQueryBuilder.append("WHERE { "); + newQueryBuilder.append(" WHERE { "); newQueryBuilder.append(processSupremeAttributes()); if (!objectOfNextOr.isEmpty()) { newQueryBuilder.append("{ "); @@ -578,13 +569,30 @@ private void buildSparqlQueryString() { LOG.trace("Translated SOQL query '{}' to SPARQL '{}'.", soql, sparql); } - private StringBuilder getCountPart(String selectParameter) { - StringBuilder countPart = new StringBuilder(" (COUNT("); - if (isSelectedParamDistinct) { - countPart.append(SoqlConstants.DISTINCT).append(' '); + private void appendProjection(StringBuilder sparqlBuilder) { + // the first attribute is either a projected parameter or a type attribute + final String selectParameter = getSelectParameter(attributes.get(0)); + if (selectProjection.aggregateFunction() != null) { + if (selectProjection.distinct()) { + sparqlBuilder.append(' ').append(SoqlConstants.DISTINCT); + } + sparqlBuilder.append(getAggregatePart(selectParameter)); + } else { + if (selectProjection.distinct()) { + sparqlBuilder.append(' ').append(SoqlConstants.DISTINCT); + } + sparqlBuilder.append(' ').append(selectParameter); + } + } + + private StringBuilder getAggregatePart(String selectParameter) { + final AggregateFunction fn = selectProjection.aggregateFunction(); + final StringBuilder aggregatePart = new StringBuilder(" (").append(fn.soqlName()).append('('); + if (selectProjection.aggregateDistinct()) { + aggregatePart.append(SoqlConstants.DISTINCT).append(' '); } - countPart.append(selectParameter).append(") AS ?count) "); - return countPart; + aggregatePart.append(selectParameter).append(") AS ").append(fn.resultVariable()).append(")"); + return aggregatePart; } private StringBuilder processSupremeAttributes() { diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/sparql/loader/SparqlQueryResultLoadingOptimizer.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/sparql/loader/SparqlQueryResultLoadingOptimizer.java index 604a63da1..fcda18984 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/sparql/loader/SparqlQueryResultLoadingOptimizer.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/sparql/loader/SparqlQueryResultLoadingOptimizer.java @@ -143,7 +143,7 @@ private boolean limitOrOffsetNotSet() { * @param descriptor Descriptor to examine * @return {@code true} if at most one context is used, {@code false} otherwise */ - private boolean descriptorSpecifiesAtMostOneContext(Descriptor descriptor) { + private static boolean descriptorSpecifiesAtMostOneContext(Descriptor descriptor) { return descriptor.getContexts().size() <= 1 && descriptor.getAttributeDescriptors().stream() .allMatch(d -> d.getContexts().size() <= 1); } diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/AbstractUnitOfWork.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/AbstractUnitOfWork.java index d6a7e6045..76b543dd0 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/AbstractUnitOfWork.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/AbstractUnitOfWork.java @@ -738,7 +738,7 @@ private Object initEntityIdentifier(Object entity, EntityType et) { Object id = getIdentifier(entity); if (id == null) { EntityPropertiesUtils.verifyIdentifierIsGenerated(entity, et); - id = storage.generateIdentifier(et); + id = getMetamodel().getIdentifierGenerator(et).generate(entity, et, storage.getConnection()); EntityPropertiesUtils.setIdentifier(id, entity, et); } return id; diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/ConnectionWrapper.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/ConnectionWrapper.java index 8211234b5..09595d494 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/ConnectionWrapper.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/sessions/ConnectionWrapper.java @@ -19,7 +19,6 @@ import cz.cvut.kbss.jopa.exceptions.OWLPersistenceException; import cz.cvut.kbss.jopa.model.descriptors.Descriptor; -import cz.cvut.kbss.jopa.model.metamodel.EntityType; import cz.cvut.kbss.jopa.model.metamodel.FieldSpecification; import cz.cvut.kbss.jopa.oom.ObjectOntologyMapper; import cz.cvut.kbss.jopa.oom.ObjectOntologyMapperImpl; @@ -76,10 +75,6 @@ public void merge(T entity, FieldSpecification fieldSpec, Desc mapper.updateFieldValue(entity, fieldSpec, descriptor); } - public URI generateIdentifier(EntityType et) { - return mapper.generateIdentifier(et); - } - public void persist(Object identifier, T entity, Descriptor descriptor) { final URI idUri = getIdentifierAsUri(identifier); mapper.persistEntity(idUri, entity, descriptor); @@ -165,6 +160,10 @@ public RepositoryMetadata getRepositoryMetadata() { return connection.getRepositoryMetadata(); } + public Connection getConnection() { + return connection; + } + @Override public T unwrap(Class cls) { try { diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/accessors/DataSourceStub.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/accessors/DataSourceStub.java index 1e8357ea2..5611845cf 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/accessors/DataSourceStub.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/accessors/DataSourceStub.java @@ -142,11 +142,6 @@ public Collection> find(AxiomDescriptor descriptor) { @Override public void persist(AxiomValueDescriptor descriptor) {} - @Override - public URI generateIdentifier(URI classUri) { - return null; - } - @Override public void update(AxiomValueDescriptor descriptor) {} diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/feature/EntityLifecycleListenersTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/feature/EntityLifecycleListenersTest.java index f817959c9..a8478f2d2 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/feature/EntityLifecycleListenersTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/feature/EntityLifecycleListenersTest.java @@ -123,8 +123,7 @@ public void prePersistLifecycleListenerIsCalledBeforeInstanceIsInsertedIntoPersi final Map mockMap = spy(new HashMap<>()); TestEnvironmentUtils.setMock(uow, AbstractUnitOfWork.class.getDeclaredField("newObjectsKeyToClone"), mockMap); final URI rId = Generators.createIndividualIdentifier(); - final OWLClassR rInstance = spy(new OWLClassR()); - when(storageMock.generateIdentifier(metamodelMock.entity(OWLClassR.class))).thenReturn(rId); + final OWLClassR rInstance = spy(new OWLClassR(rId)); uow.registerNewObject(rInstance, descriptor); final InOrder inOrder = inOrder(rInstance, parentListenerMock, concreteListenerMock, anotherListenerMock, storageMock, mockMap); @@ -156,9 +155,8 @@ private T createInstance(Class cls) { @Test public void postPersistEntityLifecycleListenerIsCalledAfterInstanceIsInsertedIntoStorage() { final URI rId = Generators.createIndividualIdentifier(); - final OWLClassR rInstance = spy(new OWLClassR()); + final OWLClassR rInstance = spy(new OWLClassR(rId)); when(cloneBuilderMock.buildClone(eq(rInstance), any(CloneConfiguration.class))).thenReturn(rInstance); - when(storageMock.generateIdentifier(metamodelMock.entity(OWLClassR.class))).thenReturn(rId); uow.registerNewObject(rInstance, descriptor); uow.commit(); final InOrder inOrder = inOrder(rInstance, concreteListenerMock, storageMock); diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/id/RandomNumberIdentifierGeneratorTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/id/RandomNumberIdentifierGeneratorTest.java new file mode 100644 index 000000000..2bf2260cc --- /dev/null +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/id/RandomNumberIdentifierGeneratorTest.java @@ -0,0 +1,128 @@ +package cz.cvut.kbss.jopa.id; + +import cz.cvut.kbss.jopa.model.IRI; +import cz.cvut.kbss.jopa.model.metamodel.EntityType; +import cz.cvut.kbss.ontodriver.Connection; +import cz.cvut.kbss.ontodriver.exception.IdentifierGenerationException; +import cz.cvut.kbss.ontodriver.exception.OntoDriverException; +import cz.cvut.kbss.ontodriver.model.Axiom; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.api.parallel.ExecutionMode; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.net.URI; +import java.util.regex.Pattern; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +@Execution(ExecutionMode.CONCURRENT) +class RandomNumberIdentifierGeneratorTest { + + private static final URI CLASS_URI = URI.create("http://example.org/entities#TestEntity"); + + @Mock + private Connection connection; + + private final RandomNumberIdentifierGenerator sut = new RandomNumberIdentifierGenerator(); + + private EntityType configureMetamodel(URI classUri) { + final EntityType entityType = mock(EntityType.class); + when(entityType.getIRI()).thenReturn(IRI.create(classUri.toString())); + return entityType; + } + + @Test + void generateReturnsUniqueIdentifierWhenItDoesNotExist() throws Exception { + final EntityType entityType = configureMetamodel(CLASS_URI); + when(connection.contains(any(Axiom.class), any())).thenReturn(false); + + final URI result = sut.generate(new TestEntity(), entityType, connection); + + assertNotNull(result); + assertTrue(result.toString().startsWith(CLASS_URI.toString())); + final ArgumentCaptor captor = ArgumentCaptor.forClass(Axiom.class); + verify(connection).contains(captor.capture(), any()); + assertEquals(CLASS_URI, captor.getValue().getValue().getValue()); + assertTrue(captor.getValue().getAssertion().isClassAssertion()); + assertFalse(captor.getValue().getAssertion().isInferred()); + } + + @Test + void generateThrowsIdentifierGenerationExceptionWhenAllGeneratedIdentifiersExist() throws Exception { + final EntityType entityType = configureMetamodel(CLASS_URI); + when(connection.contains(any(Axiom.class), any())).thenReturn(true); + + final IdentifierGenerationException ex = assertThrows(IdentifierGenerationException.class, + () -> sut.generate(new TestEntity(), entityType, connection)); + assertEquals("Unable to generate a unique identifier.", ex.getMessage()); + verify(connection, times(64)).contains(any(Axiom.class), any()); + } + + @Test + void generateRetriesUntilItFindsUniqueIdentifier() throws Exception { + final EntityType entityType = configureMetamodel(CLASS_URI); + when(connection.contains(any(Axiom.class), any())).thenReturn(true, true, false); + + final URI result = sut.generate(new TestEntity(), entityType, connection); + + assertNotNull(result); + verify(connection, times(3)).contains(any(Axiom.class), any()); + } + + @Test + void generateThrowsIdentifierGenerationExceptionWhenConnectionFails() throws Exception { + final EntityType entityType = configureMetamodel(CLASS_URI); + when(connection.contains(any(Axiom.class), any())).thenThrow(new OntoDriverException("Storage error")); + + final IdentifierGenerationException ex = assertThrows(IdentifierGenerationException.class, + () -> sut.generate(new TestEntity(), entityType, connection)); + assertTrue(ex.getMessage().contains("Unable to check if identifier")); + assertEquals(OntoDriverException.class, ex.getCause().getClass()); + } + + @ParameterizedTest + @ValueSource(strings = { + "http://example.org/Entity#Test", + "http://example.org/Entity/", + "http://example.org/Entity" + }) + void generateBuildsUriAccordingToClassIriFormat(String classUriString) throws Exception { + final URI classUri = URI.create(classUriString); + final EntityType entityType = configureMetamodel(classUri); + when(connection.contains(any(Axiom.class), any())).thenReturn(false); + + final URI result = sut.generate(new TestEntity(), entityType, connection); + + final String prefix = expectedPrefix(classUri); + assertTrue(result.toString().matches(Pattern.quote(prefix) + "-?\\d+")); + } + + private static String expectedPrefix(URI classUri) { + if (classUri.getFragment() != null) { + return classUri + "_instance"; + } else if (classUri.toString().endsWith("/")) { + return classUri + "instance"; + } else { + return classUri + "/instance"; + } + } + + private static class TestEntity { + } +} diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/model/metamodel/MetamodelBuilderTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/model/metamodel/MetamodelBuilderTest.java index 97c7109aa..b4a62e95f 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/model/metamodel/MetamodelBuilderTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/model/metamodel/MetamodelBuilderTest.java @@ -31,10 +31,12 @@ import cz.cvut.kbss.jopa.environment.utils.TestLocal; import cz.cvut.kbss.jopa.exception.InvalidFieldMappingException; import cz.cvut.kbss.jopa.exception.MetamodelInitializationException; +import cz.cvut.kbss.jopa.id.AbstractIdentifierGenerator; import cz.cvut.kbss.jopa.loaders.PersistenceUnitClassFinder; import cz.cvut.kbss.jopa.model.MultilingualString; import cz.cvut.kbss.jopa.model.annotations.FetchType; import cz.cvut.kbss.jopa.model.annotations.Id; +import cz.cvut.kbss.jopa.model.annotations.IdGenerator; import cz.cvut.kbss.jopa.model.annotations.Inferred; import cz.cvut.kbss.jopa.model.annotations.MappedSuperclass; import cz.cvut.kbss.jopa.model.annotations.Namespace; @@ -63,6 +65,7 @@ import cz.cvut.kbss.jopa.vocabulary.DC; import cz.cvut.kbss.jopa.vocabulary.RDF; import cz.cvut.kbss.jopa.vocabulary.RDFS; +import cz.cvut.kbss.ontodriver.Connection; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; @@ -77,6 +80,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.UUID; import static org.hamcrest.CoreMatchers.containsStringIgnoringCase; import static org.hamcrest.CoreMatchers.hasItem; @@ -781,4 +785,28 @@ public static class MappedSuperclassWithInferredAttribute { @OWLClass(iri = Vocabulary.CLASS_BASE + "SubClassWithInferredParent") public static class SubClassWithInferredParent extends MappedSuperclassWithInferredAttribute { } + + @Test + void buildMetamodelResolvesCustomIdentifierGeneratorForEntityClass() { + when(finderMock.getEntities()).thenReturn(Set.of(ClassWithIdGenerator.class)); + builder.buildMetamodel(finderMock); + final EntityType et = (EntityType) builder.getEntityClass(ClassWithIdGenerator.class); + assertInstanceOf(CustomIdGenerator.class, builder.getIdGenerators().get(et)); + } + + @TestLocal + @OWLClass(iri = Vocabulary.CLASS_BASE + "ClassWithIdGenerator") + public static class ClassWithIdGenerator { + @Id(generated = true) + @IdGenerator(CustomIdGenerator.class) + private URI id; + } + + public static class CustomIdGenerator extends AbstractIdentifierGenerator { + @Override + public Object generate(Object entity, EntityType entityClass, Connection connection) { + final String classIri = entityClass.getIRI().toString(); + return classIri + "_" + UUID.randomUUID().toString(); + } + } } diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/criteria/CriteriaQueryTranslateQueryTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/criteria/CriteriaQueryTranslateQueryTest.java index 30c5c2be6..882dc3f17 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/criteria/CriteriaQueryTranslateQueryTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/criteria/CriteriaQueryTranslateQueryTest.java @@ -25,6 +25,7 @@ import cz.cvut.kbss.jopa.environment.OWLClassF; import cz.cvut.kbss.jopa.environment.OWLClassM; import cz.cvut.kbss.jopa.environment.OWLClassU; +import cz.cvut.kbss.jopa.environment.Person; import cz.cvut.kbss.jopa.environment.utils.Generators; import cz.cvut.kbss.jopa.environment.utils.MetamodelMocks; import cz.cvut.kbss.jopa.model.CriteriaQueryImpl; @@ -64,12 +65,13 @@ public class CriteriaQueryTranslateQueryTest { @Mock private UnitOfWork uowMock; + private MetamodelImpl metamodel; private CriteriaBuilderImpl cb; private CriteriaParameterFiller criteriaParameterFiller; @BeforeEach void setUp() throws Exception { - final MetamodelImpl metamodel = mock(MetamodelImpl.class); + this.metamodel = mock(MetamodelImpl.class); new MetamodelMocks().setMocks(metamodel); final MetamodelProvider mpp = mock(MetamodelProvider.class); when(uowMock.getMetamodel()).thenReturn(metamodel); @@ -92,6 +94,17 @@ public void testTranslateQuerySelectAll() { assertEquals(expectedSoqlQuery, generatedSoqlQuery); } + @Test + void translateSelectFromEntityType() { + CriteriaQueryImpl query = cb.createQuery(OWLClassA.class); + Root root = query.from(metamodel.entity(OWLClassA.class)); + query.select(root); + + final String generatedSoqlQuery = query.translateQuery(criteriaParameterFiller); + final String expectedSoqlQuery = "SELECT owlclassa FROM OWLClassA owlclassa"; + assertEquals(expectedSoqlQuery, generatedSoqlQuery); + } + @Test public void testTranslateQuerySelectDistinctAll() { CriteriaQueryImpl query = cb.createQuery(OWLClassA.class); @@ -125,6 +138,17 @@ public void testTranslateQueryDistinctCount() { assertEquals(expectedSoqlQuery, generatedSoqlQuery); } + @Test + void testTranslationQueryCountDistinct() { + CriteriaQueryImpl query = cb.createQuery(Integer.class); + Root root = query.from(OWLClassA.class); + query.select(cb.countDistinct(root)); + + final String generatedSoqlQuery = query.translateQuery(criteriaParameterFiller); + final String expectedSoqlQuery = "SELECT COUNT(DISTINCT owlclassa) FROM OWLClassA owlclassa"; + assertEquals(expectedSoqlQuery, generatedSoqlQuery); + } + @Test public void testTranslateQuerySelectAllOrderByEntityDesc() { CriteriaQueryImpl query = cb.createQuery(OWLClassA.class); @@ -136,6 +160,17 @@ public void testTranslateQuerySelectAllOrderByEntityDesc() { assertEquals(expectedSoqlQuery, generatedSoqlQuery); } + @Test + void translateQuerySupportsOrderByMultipleConditions() { + CriteriaQueryImpl query = cb.createQuery(OWLClassM.class); + Root root = query.from(OWLClassM.class); + query.select(root).orderBy(List.of(cb.asc(root.getAttr("intAttribute")), cb.asc(root.getAttr("longAttribute")))); + + final String generatedSoqlQuery = query.translateQuery(criteriaParameterFiller); + final String expectedSoqlQuery = "SELECT owlclassm FROM OWLClassM owlclassm ORDER BY owlclassm.intAttribute ASC, owlclassm.longAttribute ASC"; + assertEquals(expectedSoqlQuery, generatedSoqlQuery); + } + @Test void translateQuerySupportsLangMatches() { CriteriaQueryImpl query = cb.createQuery(OWLClassA.class); @@ -694,4 +729,30 @@ public void testTranslateQueryRestrictionWithParameterExpression() { assertEquals(expectedSoqlQuery, generatedSoqlQuery); } } + + @Nested + class AskQueryTests { + + @Test + void testTranslateAskQuery() { + CriteriaQueryImpl query = cb.createQuery(Boolean.class); + Root root = query.from(Person.class); + query.ask().where(cb.greaterThan(root.getAttr("age"), cb.literal(18))); + + final String generatedSoqlQuery = query.translateQuery(criteriaParameterFiller); + final String expectedSoqlQuery = "ASK FROM Person person WHERE person.age > :generatedName0"; + assertEquals(expectedSoqlQuery, generatedSoqlQuery); + } + + @Test + void askOverridesSelection() { + CriteriaQueryImpl query = cb.createQuery(Boolean.class); + Root root = query.from(OWLClassM.class); + query.select(root.getAttr("booleanAttribute")).ask().where(cb.lessThan(root.getAttr("intAttribute"), cb.literal(100))); + + final String generatedSoqlQuery = query.translateQuery(criteriaParameterFiller); + final String expectedSoqlQuery = "ASK FROM OWLClassM owlclassm WHERE owlclassm.intAttribute < :generatedName0"; + assertEquals(expectedSoqlQuery, generatedSoqlQuery); + } + } } diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/soql/SoqlQueryParserTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/soql/SoqlQueryParserTest.java index 1cddf1a33..6128ac947 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/soql/SoqlQueryParserTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/query/soql/SoqlQueryParserTest.java @@ -111,6 +111,16 @@ public void testParseCountQuery() { @Test public void testParseDistinctCountQuery() { final String soqlQuery = "SELECT DISTINCT COUNT(p) FROM Person p"; + final String expectedSparqlQuery = + "SELECT DISTINCT (COUNT(?x) AS ?count) WHERE { ?x a " + strUri(Vocabulary.c_Person) + " . }"; + final QueryHolder holder = sut.parseQuery(soqlQuery); + assertEquals(expectedSparqlQuery, holder.getQuery()); + assertEquals(2, holder.getParameters().size()); + } + + @Test + void testParseCountDistinctQuery() { + final String soqlQuery = "SELECT COUNT(DISTINCT p) FROM Person p"; final String expectedSparqlQuery = "SELECT (COUNT(DISTINCT ?x) AS ?count) WHERE { ?x a " + strUri(Vocabulary.c_Person) + " . }"; final QueryHolder holder = sut.parseQuery(soqlQuery); @@ -769,8 +779,8 @@ void parseQuerySupportsCountWithProjectedAttribute() { } @Test - void parseQuerySupportsDistinctCountWithProjectedAttribute() { - final String soqlIdFirst = "SELECT DISTINCT COUNT(d.owlClassA) FROM OWLClassD d WHERE d.uri = :uri"; + void parseQuerySupportsCountDistinctWithProjectedAttribute() { + final String soqlIdFirst = "SELECT COUNT(DISTINCT d.owlClassA) FROM OWLClassD d WHERE d.uri = :uri"; final String expectedSparql = "SELECT (COUNT(DISTINCT ?owlClassA) AS ?count) WHERE { ?uri a " + strUri(Vocabulary.c_OwlClassD) + " . " + "?uri " + strUri(Vocabulary.p_h_hasA) + " ?owlClassA . }"; parseAndAssertEquality(expectedSparql, soqlIdFirst); @@ -1019,4 +1029,21 @@ void parseQuerySupportsOrderingByEntity() { final String expectedSparql = "SELECT ?x WHERE { ?x a " + strUri(Vocabulary.c_Person) + " . } ORDER BY ?x"; parseAndAssertEquality(expectedSparql, soql); } + + @Test + void parseQuerySupportsBasicAskQueries() { + final String soql = "ASK FROM Person p WHERE p.age > :age"; + final String expectedSparql = "ASK WHERE { ?x a " + strUri(Vocabulary.c_Person) + + " . ?x " + strUri(Vocabulary.p_p_age) + " ?pAge . FILTER (?pAge > ?age) }"; + parseAndAssertEquality(expectedSparql, soql); + } + + @Test + void parseQuerySupportsMoreComplexAskQueries() { + final String soql = "ASK FROM Person p WHERE p.age > :age AND p.gender = :gender"; + final String expectedSparql = "ASK WHERE { ?x a " + strUri(Vocabulary.c_Person) + + " . ?x " + strUri(Vocabulary.p_p_age) + " ?pAge . ?x " + strUri(Vocabulary.p_p_gender) + " ?gender . " + + "FILTER (?pAge > ?age) }"; + parseAndAssertEquality(expectedSparql, soql); + } } diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/ReadWriteUnitOfWorkTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/ReadWriteUnitOfWorkTest.java index bb7aa61c0..b17e31caf 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/ReadWriteUnitOfWorkTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/sessions/ReadWriteUnitOfWorkTest.java @@ -29,6 +29,7 @@ import cz.cvut.kbss.jopa.exception.IdentifierNotSetException; import cz.cvut.kbss.jopa.exceptions.CardinalityConstraintViolatedException; import cz.cvut.kbss.jopa.exceptions.EntityNotFoundException; +import cz.cvut.kbss.jopa.id.IdentifierGenerator; import cz.cvut.kbss.jopa.model.EntityState; import cz.cvut.kbss.jopa.model.LoadState; import cz.cvut.kbss.jopa.model.MultilingualString; @@ -44,6 +45,7 @@ import cz.cvut.kbss.jopa.sessions.util.CloneConfiguration; import cz.cvut.kbss.jopa.sessions.util.CloneRegistrationDescriptor; import cz.cvut.kbss.jopa.sessions.util.LoadingParameters; +import cz.cvut.kbss.ontodriver.Connection; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; import org.mockito.Mockito; @@ -157,10 +159,14 @@ void registerNewObjectGeneratesIdentifierWhenInstancesDoesNotHaveOne() { final OWLClassE entity = new OWLClassE(); entity.setStringAttribute("Test value"); final URI id = Generators.createIndividualIdentifier(); - when(storageMock.generateIdentifier(any(EntityType.class))).thenReturn(id); + final IdentifierGenerator idGenerator = mock(IdentifierGenerator.class); + when(idGenerator.generate(eq(entity), any(), any())).thenReturn(id); + final Connection conn = mock(Connection.class); + when(storageMock.getConnection()).thenReturn(conn); + when(metamodelMock.getIdentifierGenerator(any(EntityType.class))).thenReturn(idGenerator); uow.registerNewObject(entity, descriptor); assertEquals(id, entity.getUri()); - verify(storageMock).generateIdentifier(metamodelMocks.forOwlClassE().entityType()); + verify(idGenerator).generate(entity, metamodelMocks.forOwlClassE().entityType(), conn); } @Test diff --git a/jopa-integration-tests-jena/pom.xml b/jopa-integration-tests-jena/pom.xml index 4fc7865f3..d0595f5e3 100644 --- a/jopa-integration-tests-jena/pom.xml +++ b/jopa-integration-tests-jena/pom.xml @@ -5,7 +5,7 @@ cz.cvut.kbss.jopa jopa-all - 2.10.1 + 2.11.0 ../pom.xml 4.0.0 diff --git a/jopa-integration-tests-owlapi/pom.xml b/jopa-integration-tests-owlapi/pom.xml index 5640206da..9e379a8aa 100644 --- a/jopa-integration-tests-owlapi/pom.xml +++ b/jopa-integration-tests-owlapi/pom.xml @@ -5,7 +5,7 @@ cz.cvut.kbss.jopa jopa-all - 2.10.1 + 2.11.0 ../pom.xml 4.0.0 diff --git a/jopa-integration-tests-owlapi/src/test/java/cz/cvut/kbss/jopa/test/query/owlapi/TypedQueryTest.java b/jopa-integration-tests-owlapi/src/test/java/cz/cvut/kbss/jopa/test/query/owlapi/TypedQueryTest.java index 5141384e8..0b0594351 100644 --- a/jopa-integration-tests-owlapi/src/test/java/cz/cvut/kbss/jopa/test/query/owlapi/TypedQueryTest.java +++ b/jopa-integration-tests-owlapi/src/test/java/cz/cvut/kbss/jopa/test/query/owlapi/TypedQueryTest.java @@ -148,4 +148,11 @@ public void querySupportsNamedFetchGraphs() { public void querySupportsNestedNamedFetchGraphs() { // OWL2Query does not support OPTIONAL } + + @Disabled + @Override + public void querySupportsMappingResultsDirectlyToEntity() { + // OWL2Query does not seem to correctly implement ORDER BY, causing result rows not always to be consecutive by subject identifier + // And thus the query sometimes fails + } } diff --git a/jopa-integration-tests-rdf4j/pom.xml b/jopa-integration-tests-rdf4j/pom.xml index 6af4babe8..4c98a6206 100644 --- a/jopa-integration-tests-rdf4j/pom.xml +++ b/jopa-integration-tests-rdf4j/pom.xml @@ -6,7 +6,7 @@ cz.cvut.kbss.jopa jopa-all - 2.10.1 + 2.11.0 ../pom.xml jopa-integration-tests-rdf4j diff --git a/jopa-integration-tests-virtuoso/pom.xml b/jopa-integration-tests-virtuoso/pom.xml index 5dd53b300..39e7acc38 100644 --- a/jopa-integration-tests-virtuoso/pom.xml +++ b/jopa-integration-tests-virtuoso/pom.xml @@ -6,7 +6,7 @@ cz.cvut.kbss.jopa jopa-all - 2.10.1 + 2.11.0 jopa-integration-tests-virtuoso diff --git a/jopa-integration-tests/pom.xml b/jopa-integration-tests/pom.xml index 7221bca0f..f5ef36c04 100644 --- a/jopa-integration-tests/pom.xml +++ b/jopa-integration-tests/pom.xml @@ -6,7 +6,7 @@ cz.cvut.kbss.jopa jopa-all - 2.10.1 + 2.11.0 ../pom.xml 4.0.0 diff --git a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/OWLClassZChild.java b/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/OWLClassZChild.java index 3b4b34e28..be663fbba 100644 --- a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/OWLClassZChild.java +++ b/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/OWLClassZChild.java @@ -17,14 +17,18 @@ */ package cz.cvut.kbss.jopa.test; +import cz.cvut.kbss.jopa.id.AbstractIdentifierGenerator; import cz.cvut.kbss.jopa.model.annotations.CascadeType; import cz.cvut.kbss.jopa.model.annotations.FetchType; import cz.cvut.kbss.jopa.model.annotations.Id; +import cz.cvut.kbss.jopa.model.annotations.IdGenerator; import cz.cvut.kbss.jopa.model.annotations.OWLClass; import cz.cvut.kbss.jopa.model.annotations.OWLDataProperty; import cz.cvut.kbss.jopa.model.annotations.OWLObjectProperty; import cz.cvut.kbss.jopa.model.annotations.ParticipationConstraints; +import cz.cvut.kbss.jopa.model.metamodel.EntityType; import cz.cvut.kbss.jopa.vocabulary.RDFS; +import cz.cvut.kbss.ontodriver.Connection; import java.net.URI; import java.util.HashSet; @@ -34,7 +38,8 @@ @OWLClass(iri = Vocabulary.C_OWL_CLASS_Z_CHILD) public class OWLClassZChild { - @Id + @Id(generated = true) + @IdGenerator(NamedBasedIdGenerator.class) private URI id; @ParticipationConstraints(nonEmpty = true) @@ -92,4 +97,15 @@ public String toString() { ", name='" + name + '\'' + '}'; } + + public static class NamedBasedIdGenerator extends AbstractIdentifierGenerator { + + @Override + public Object generate(Object entity, EntityType entityClass, Connection connection) { + final String name = ((OWLClassZChild) entity).getName(); + final URI result = URI.create(Vocabulary.C_OWL_CLASS_Z_CHILD + "/" + name); + assert !exists(result, entityClass.getIRI().toURI(), connection); + return result; + } + } } diff --git a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/query/runner/CriteriaRunner.java b/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/query/runner/CriteriaRunner.java index 2d6ffe161..5da1d97fc 100644 --- a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/query/runner/CriteriaRunner.java +++ b/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/query/runner/CriteriaRunner.java @@ -584,8 +584,8 @@ void testSelectionByLangMatches() { @Test void testSelectionByIdentifierInCollection() { - final List aInstances = QueryTestEnvironment.getData(OWLClassM.class); - final List expected = aInstances.subList(1, 3); + final List mInstances = QueryTestEnvironment.getData(OWLClassM.class); + final List expected = mInstances.subList(1, 3); final CriteriaBuilder cb = getEntityManager().getCriteriaBuilder(); final CriteriaQuery query = cb.createQuery(OWLClassM.class); final Root root = query.from(OWLClassM.class); @@ -597,4 +597,19 @@ void testSelectionByIdentifierInCollection() { final List result = tq.getResultList(); assertEquals(expected.size(), result.size()); } + + @Test + void testAskQuery() { + final List mInstances = QueryTestEnvironment.getData(OWLClassM.class); + final Optional max = mInstances.stream().map(OWLClassM::getIntAttribute).max(Integer::compare); + assertTrue(max.isPresent()); + final CriteriaBuilder cb = getEntityManager().getCriteriaBuilder(); + final CriteriaQuery query = cb.createQuery(Boolean.class); + final Root root = query.from(OWLClassM.class); + query.ask().where(cb.greaterThanOrEqual(root.getAttr("intAttribute"), cb.literal(max.get()))); + + final TypedQuery tq = getEntityManager().createQuery(query); + final Boolean result = tq.getSingleResult(); + assertTrue(result); + } } diff --git a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/query/runner/TypedQueryRunner.java b/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/query/runner/TypedQueryRunner.java index 7f18dd2f9..ffa74a6f9 100644 --- a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/query/runner/TypedQueryRunner.java +++ b/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/query/runner/TypedQueryRunner.java @@ -449,7 +449,7 @@ public void querySupportsMappingResultsDirectlyToEntity() { ?x a ?type ; ?hasString ?stringAttribute; a ?types . - }""", OWLClassA.class) + } ORDER BY ?x""", OWLClassA.class) .setParameter("type", URI.create(Vocabulary.C_OWL_CLASS_A)) .setParameter("hasString", URI.create(Vocabulary.P_A_STRING_ATTRIBUTE)) .getResultList(); diff --git a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/runner/CreateOperationsRunner.java b/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/runner/CreateOperationsRunner.java index 46cdc8927..14782192a 100644 --- a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/runner/CreateOperationsRunner.java +++ b/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/runner/CreateOperationsRunner.java @@ -40,6 +40,7 @@ import cz.cvut.kbss.jopa.test.OWLClassWithQueryAttr4; import cz.cvut.kbss.jopa.test.OWLClassWithQueryAttr5; import cz.cvut.kbss.jopa.test.OWLClassX; +import cz.cvut.kbss.jopa.test.OWLClassZChild; import cz.cvut.kbss.jopa.test.Vocabulary; import cz.cvut.kbss.jopa.test.environment.DataAccessor; import cz.cvut.kbss.jopa.test.environment.Generators; @@ -766,4 +767,17 @@ void persistSupportsResourceAnnotationPropertiesWithFieldOfTypeObject() throws E new Quad(URI.create(entityM.getKey()), URI.create(Vocabulary.p_m_objectAnnotationProperty), entityM.getObjectAnnotation())), em); } + + @Test + void persistUsesCustomIdentifierGeneratorDeclaredForEntityClass() { + this.em = getEntityManager("persistUsesCustomIdentifierGeneratorDeclaredForEntityClass", false); + final OWLClassZChild instance = new OWLClassZChild(); + instance.setName("test-instance"); + persist(instance); + + assertNotNull(instance.getId()); + assertEquals(Vocabulary.C_OWL_CLASS_Z_CHILD + "/test-instance", instance.getId().toString()); + final OWLClassZChild result = findRequired(OWLClassZChild.class, instance.getId()); + assertNotNull(result); + } } diff --git a/jopa-maven-plugin/pom.xml b/jopa-maven-plugin/pom.xml index 59d68d92c..8b390ae04 100644 --- a/jopa-maven-plugin/pom.xml +++ b/jopa-maven-plugin/pom.xml @@ -5,7 +5,7 @@ jopa-all cz.cvut.kbss.jopa - 2.10.1 + 2.11.0 ../pom.xml 4.0.0 diff --git a/jopa-owl2java/pom.xml b/jopa-owl2java/pom.xml index 8978a5ea8..f9a96868d 100644 --- a/jopa-owl2java/pom.xml +++ b/jopa-owl2java/pom.xml @@ -6,7 +6,7 @@ cz.cvut.kbss.jopa jopa-all - 2.10.1 + 2.11.0 ../pom.xml diff --git a/jopa-owlapi-utils/pom.xml b/jopa-owlapi-utils/pom.xml index a753b7ba3..437a7f19b 100644 --- a/jopa-owlapi-utils/pom.xml +++ b/jopa-owlapi-utils/pom.xml @@ -6,7 +6,7 @@ cz.cvut.kbss.jopa jopa-all - 2.10.1 + 2.11.0 ../pom.xml diff --git a/modelgen/pom.xml b/modelgen/pom.xml index 6e1ae39fb..817ed96d4 100644 --- a/modelgen/pom.xml +++ b/modelgen/pom.xml @@ -6,7 +6,7 @@ jopa-all cz.cvut.kbss.jopa - 2.10.1 + 2.11.0 ../pom.xml diff --git a/modelgen/src/main/java/cz/cvut/kbss/jopa/modelgen/OutputFilesGenerator.java b/modelgen/src/main/java/cz/cvut/kbss/jopa/modelgen/OutputFilesGenerator.java index 12b92d974..599767498 100644 --- a/modelgen/src/main/java/cz/cvut/kbss/jopa/modelgen/OutputFilesGenerator.java +++ b/modelgen/src/main/java/cz/cvut/kbss/jopa/modelgen/OutputFilesGenerator.java @@ -32,7 +32,6 @@ import java.util.Collection; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Optional; import java.util.Set; @@ -217,56 +216,17 @@ private static String generateAttributes(MetamodelClass cls) { attributes.append(INDENT + "public static volatile "); //@Id if (field.isAnnotatedWith(MappingAnnotation.ID)) { - attributes.append("Identifier<") - .append(declaringClass) - .append(", ") - .append(field.getType().getTypeName() - .substring(field.getType().getTypeName().lastIndexOf(".") + 1)); + outputIdentifierField(field, attributes, declaringClass); //@Types } else if (field.isAnnotatedWith(MappingAnnotation.TYPES)) { - attributes.append("TypesSpecification<") - .append(declaringClass) - .append(", "); - if (field.getType().getIsSimple()) { - attributes.append(field.getType().getSimpleName()); - } else { - attributes.append(field.getType().getTypes().get(0).getSimpleName()); - } + outputTypesField(field, attributes, declaringClass); //@Properties } else if (field.isAnnotatedWith(MappingAnnotation.PROPERTIES)) { - attributes.append("PropertiesSpecification<") - .append(declaringClass) - .append(", "); - Type type = field.getType(); - if (!Objects.equals(type.getTypeName(), Map.class.getName())) { - attributes - .append(type.getTypes().get(0).getTypeName() - .substring(type.getTypes().get(0).getTypeName().lastIndexOf(".") + 1)); - } else { - attributes.append("Map, ") - .append(type.getTypes().get(0).getSimpleName()) - .append(", ") - .append(type.getTypes().get(1).getTypes().get(0).getSimpleName()); - } + outputUnmappedPropertiesField(field, attributes, declaringClass); } else if (field.isAnnotatedWith(MappingAnnotation.DATA_PROPERTY) || field.isAnnotatedWith(MappingAnnotation.OBJECT_PROPERTY) || field.isAnnotatedWith(MappingAnnotation.ANNOTATION_PROPERTY)) { - Type type = field.getType(); - if (type.getIsSimple()) { - attributes.append("SingularAttribute<") - .append(declaringClass) - .append(", ") - .append(type.getSimpleName()); - } else { - if (type.getTypeName().equals(List.class.getName())) { - attributes.append("ListAttribute<"); - } else if (type.getTypeName().equals(Set.class.getName())) { - attributes.append("SetAttribute<"); - } - attributes.append(declaringClass) - .append(", ") - .append(type.getTypes().get(0).getSimpleName()); - } + outputMappedAttributeField(field, attributes, declaringClass); } attributes .append("> ") @@ -275,4 +235,59 @@ private static String generateAttributes(MetamodelClass cls) { } return attributes.toString(); } + + private static void outputIdentifierField(Field field, StringBuilder attributes, String declaringClass) { + attributes.append("Identifier<") + .append(declaringClass) + .append(", ") + .append(field.getType().getTypeName() + .substring(field.getType().getTypeName().lastIndexOf(".") + 1)); + } + + private static void outputTypesField(Field field, StringBuilder attributes, String declaringClass) { + attributes.append("TypesSpecification<") + .append(declaringClass) + .append(", "); + if (field.getType().getIsSimple()) { + attributes.append(field.getType().getSimpleName()); + } else { + attributes.append(field.getType().getTypes().get(0).getSimpleName()); + } + } + + private static void outputUnmappedPropertiesField(Field field, StringBuilder attributes, String declaringClass) { + attributes.append("PropertiesSpecification<") + .append(declaringClass) + .append(", "); + Type type = field.getType(); + if (!Map.class.getName().equals(type.getTypeName())) { + attributes + .append(type.getTypes().get(0).getTypeName() + .substring(type.getTypes().get(0).getTypeName().lastIndexOf(".") + 1)); + } else { + attributes.append("Map, ") + .append(type.getTypes().get(0).getSimpleName()) + .append(", ") + .append(type.getTypes().get(1).getTypes().get(0).getSimpleName()); + } + } + + private static void outputMappedAttributeField(Field field, StringBuilder attributes, String declaringClass) { + Type type = field.getType(); + if (type.getIsSimple()) { + attributes.append("SingularAttribute<") + .append(declaringClass) + .append(", ") + .append(type.getSimpleName()); + } else { + if (type.getTypeName().equals(List.class.getName())) { + attributes.append("ListAttribute<"); + } else if (type.getTypeName().equals(Set.class.getName())) { + attributes.append("SetAttribute<"); + } + attributes.append(declaringClass) + .append(", ") + .append(type.getTypes().get(0).getSimpleName()); + } + } } diff --git a/ontodriver-api/pom.xml b/ontodriver-api/pom.xml index 08539c3a6..1473c79d9 100644 --- a/ontodriver-api/pom.xml +++ b/ontodriver-api/pom.xml @@ -6,7 +6,7 @@ jopa-all cz.cvut.kbss.jopa - 2.10.1 + 2.11.0 ../pom.xml diff --git a/ontodriver-api/src/main/java/cz/cvut/kbss/ontodriver/Connection.java b/ontodriver-api/src/main/java/cz/cvut/kbss/ontodriver/Connection.java index 5f1ebfa43..ca53547e6 100644 --- a/ontodriver-api/src/main/java/cz/cvut/kbss/ontodriver/Connection.java +++ b/ontodriver-api/src/main/java/cz/cvut/kbss/ontodriver/Connection.java @@ -187,17 +187,6 @@ public interface Connection extends AutoCloseable, Wrapper { */ void persist(AxiomValueDescriptor descriptor) throws OntoDriverException; - /** - * Generates a new unique identifier based on the specified type. - *

- * The identifier is required to be unique in the whole repository. - * - * @param classUri OWL class identifier - * @return Unique identifier - * @throws OntoDriverException If an ontology access error occurs - */ - URI generateIdentifier(URI classUri) throws OntoDriverException; - /** * Persists the values specified by this descriptor, removing existing property values from the ontology. *

diff --git a/ontodriver-api/src/main/java/cz/cvut/kbss/ontodriver/exception/IdentifierGenerationException.java b/ontodriver-api/src/main/java/cz/cvut/kbss/ontodriver/exception/IdentifierGenerationException.java index 9518de5e7..46d94f991 100644 --- a/ontodriver-api/src/main/java/cz/cvut/kbss/ontodriver/exception/IdentifierGenerationException.java +++ b/ontodriver-api/src/main/java/cz/cvut/kbss/ontodriver/exception/IdentifierGenerationException.java @@ -22,4 +22,8 @@ public class IdentifierGenerationException extends OntoDriverRuntimeException { public IdentifierGenerationException(String message) { super(message); } + + public IdentifierGenerationException(String message, Throwable cause) { + super(message, cause); + } } diff --git a/ontodriver-api/src/main/java/cz/cvut/kbss/ontodriver/util/IdentifierUtils.java b/ontodriver-api/src/main/java/cz/cvut/kbss/ontodriver/util/IdentifierUtils.java index fcd4a478a..0887ca5ba 100644 --- a/ontodriver-api/src/main/java/cz/cvut/kbss/ontodriver/util/IdentifierUtils.java +++ b/ontodriver-api/src/main/java/cz/cvut/kbss/ontodriver/util/IdentifierUtils.java @@ -21,7 +21,6 @@ import java.net.URI; import java.net.URL; -import java.util.Objects; import java.util.Random; import java.util.Set; @@ -38,29 +37,6 @@ private IdentifierUtils() { throw new AssertionError(); } - /** - * Generates a (pseudo) random identifier based on the specified class URI. - *

- * The identifier consists of the class URI and then contains the string 'instance' and a random integer to ensure - * uniqueness. The 'instance' part is appended after a slash or a _, if the class URI contains a hash fragment. - * - * @param classUri Class URI used as identifier base - * @return Generated identifier - */ - public static URI generateIdentifier(URI classUri) { - Objects.requireNonNull(classUri); - if (classUri.getFragment() != null) { - return URI.create(classUri + "_instance" + RANDOM.nextInt()); - } else { - String base = classUri.toString(); - if (base.endsWith("/")) { - return URI.create(base + "instance" + RANDOM.nextInt()); - } else { - return URI.create(base + "/instance" + RANDOM.nextInt()); - } - } - } - /** * Generates a (pseudo) random integer. * diff --git a/ontodriver-api/src/test/java/cz/cvut/kbss/ontodriver/util/IdentifierUtilsTest.java b/ontodriver-api/src/test/java/cz/cvut/kbss/ontodriver/util/IdentifierUtilsTest.java deleted file mode 100644 index 858c4e12b..000000000 --- a/ontodriver-api/src/test/java/cz/cvut/kbss/ontodriver/util/IdentifierUtilsTest.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * JOPA - * Copyright (C) 2026 Czech Technical University in Prague - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3.0 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library. - */ -package cz.cvut.kbss.ontodriver.util; - -import org.junit.jupiter.api.Test; - -import java.net.URI; - -import static org.hamcrest.CoreMatchers.containsString; -import static org.hamcrest.MatcherAssert.assertThat; - -class IdentifierUtilsTest { - - private static final String URI_WITH_SLASH = "http://onto.fel.cvut.cz/ontologies/jopa/ClassA"; - - @Test - void generateIdentifierAppendsInstanceIdentifierToUriWithHashFragment() { - final URI clsUri = URI.create("http://onto.fel.cvut.cz/ontologies/jopa#IdentifierUtilsTest"); - final URI result = IdentifierUtils.generateIdentifier(clsUri); - assertThat(result.toString(), containsString("_instance")); - } - - @Test - void generateIdentifierAppendsIdentifierToUriEndingWithSlash() { - final URI clsUri = URI.create(URI_WITH_SLASH); - final URI result = IdentifierUtils.generateIdentifier(clsUri); - assertThat(result.toString(), containsString("/instance")); - } - - @Test - void generateIdentifierAppendsIdentifierWithSlashToUriWithoutHashFragment() { - final URI clsUri = URI.create(URI_WITH_SLASH); - final URI result = IdentifierUtils.generateIdentifier(clsUri); - assertThat(result.toString(), containsString("/instance")); - } -} diff --git a/ontodriver-jena/pom.xml b/ontodriver-jena/pom.xml index 15b9d5214..361c08803 100644 --- a/ontodriver-jena/pom.xml +++ b/ontodriver-jena/pom.xml @@ -8,7 +8,7 @@ cz.cvut.kbss.jopa jopa-all - 2.10.1 + 2.11.0 ../pom.xml diff --git a/ontodriver-jena/src/main/java/cz/cvut/kbss/ontodriver/jena/JenaAdapter.java b/ontodriver-jena/src/main/java/cz/cvut/kbss/ontodriver/jena/JenaAdapter.java index ef0a407c5..2afc99201 100644 --- a/ontodriver-jena/src/main/java/cz/cvut/kbss/ontodriver/jena/JenaAdapter.java +++ b/ontodriver-jena/src/main/java/cz/cvut/kbss/ontodriver/jena/JenaAdapter.java @@ -30,7 +30,6 @@ import cz.cvut.kbss.ontodriver.jena.list.ReferencedListHandler; import cz.cvut.kbss.ontodriver.jena.list.SimpleListHandler; import cz.cvut.kbss.ontodriver.jena.query.AbstractResultSet; -import cz.cvut.kbss.ontodriver.jena.util.IdentifierGenerator; import cz.cvut.kbss.ontodriver.model.Axiom; import cz.cvut.kbss.ontodriver.util.Transaction; import org.apache.jena.query.Query; @@ -118,11 +117,6 @@ List getContext() { return connector.getContexts().stream().map(URI::create).collect(Collectors.toList()); } - URI generateIdentifier(URI classUri) { - beginTransactionIfNotActive(); - return new IdentifierGenerator(connector).generateIdentifier(classUri); - } - boolean isConsistent(URI context) { beginTransactionIfNotActive(); return inferenceConnector.isConsistent(context != null ? context.toString() : null); diff --git a/ontodriver-jena/src/main/java/cz/cvut/kbss/ontodriver/jena/JenaConnection.java b/ontodriver-jena/src/main/java/cz/cvut/kbss/ontodriver/jena/JenaConnection.java index 65950952f..5738a4aa8 100644 --- a/ontodriver-jena/src/main/java/cz/cvut/kbss/ontodriver/jena/JenaConnection.java +++ b/ontodriver-jena/src/main/java/cz/cvut/kbss/ontodriver/jena/JenaConnection.java @@ -178,13 +178,6 @@ public void persist(AxiomValueDescriptor descriptor) throws JenaDriverException } } - @Override - public URI generateIdentifier(URI classUri) { - ensureOpen(); - Objects.requireNonNull(classUri); - return adapter.generateIdentifier(classUri); - } - @Override public void update(AxiomValueDescriptor descriptor) throws OntoDriverException { ensureOpen(); diff --git a/ontodriver-jena/src/main/java/cz/cvut/kbss/ontodriver/jena/util/IdentifierGenerator.java b/ontodriver-jena/src/main/java/cz/cvut/kbss/ontodriver/jena/util/IdentifierGenerator.java deleted file mode 100644 index 97d07261d..000000000 --- a/ontodriver-jena/src/main/java/cz/cvut/kbss/ontodriver/jena/util/IdentifierGenerator.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * JOPA - * Copyright (C) 2026 Czech Technical University in Prague - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3.0 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library. - */ -package cz.cvut.kbss.ontodriver.jena.util; - -import cz.cvut.kbss.ontodriver.exception.IdentifierGenerationException; -import cz.cvut.kbss.ontodriver.jena.connector.StorageConnector; -import cz.cvut.kbss.ontodriver.util.IdentifierUtils; -import cz.cvut.kbss.ontodriver.util.Vocabulary; -import org.apache.jena.rdf.model.Property; -import org.apache.jena.rdf.model.RDFNode; -import org.apache.jena.rdf.model.ResourceFactory; - -import java.net.URI; -import java.util.Collections; - -public class IdentifierGenerator { - - private static final int GENERATOR_THRESHOLD = 64; - - private final StorageConnector storageConnector; - - public IdentifierGenerator(StorageConnector storageConnector) { - this.storageConnector = storageConnector; - } - - /** - * Generates a unique identifier based on the specified class URI. - * - * @param classUri Type URI, used as the identifier base - * @return Generated identifier - */ - public URI generateIdentifier(URI classUri) { - int i = 0; - boolean exists; - final Property property = ResourceFactory.createProperty(Vocabulary.RDF_TYPE); - final RDFNode type = ResourceFactory.createResource(classUri.toString()); - URI result; - do { - result = IdentifierUtils.generateIdentifier(classUri); - exists = storageConnector.contains(ResourceFactory.createResource(result.toString()), property, type, - Collections.emptySet()); - i++; - } while (exists && i < GENERATOR_THRESHOLD); - if (i >= GENERATOR_THRESHOLD) { - throw new IdentifierGenerationException( - "Failed to generate a unique identifier in " + GENERATOR_THRESHOLD + " attempts."); - } - return result; - } -} diff --git a/ontodriver-jena/src/test/java/cz/cvut/kbss/ontodriver/jena/JenaConnectionTest.java b/ontodriver-jena/src/test/java/cz/cvut/kbss/ontodriver/jena/JenaConnectionTest.java index 5533c8f6d..608866201 100644 --- a/ontodriver-jena/src/test/java/cz/cvut/kbss/ontodriver/jena/JenaConnectionTest.java +++ b/ontodriver-jena/src/test/java/cz/cvut/kbss/ontodriver/jena/JenaConnectionTest.java @@ -210,21 +210,6 @@ public void getContextsThrowsIllegalStateExceptionForClosedConnection() throws E } } - @Test - public void generateIdentifierCallsAdapter() { - final URI uri = Generator.generateUri(); - when(adapterMock.generateIdentifier(uri)).thenReturn(uri); - final URI result = connection.generateIdentifier(uri); - assertNotNull(result); - verify(adapterMock).generateIdentifier(uri); - } - - @Test - public void generateIdentifierThrowsIllegalStateExceptionForClosedConnection() throws Exception { - connection.close(); - assertThrows(IllegalStateException.class, () -> connection.generateIdentifier(Generator.generateUri())); - } - @Test public void findCallsAdapterWithDescriptor() throws Exception { final AxiomDescriptor descriptor = new AxiomDescriptor(SUBJECT); diff --git a/ontodriver-jena/src/test/java/cz/cvut/kbss/ontodriver/jena/util/IdentifierGeneratorTest.java b/ontodriver-jena/src/test/java/cz/cvut/kbss/ontodriver/jena/util/IdentifierGeneratorTest.java deleted file mode 100644 index 0cc0ba2cd..000000000 --- a/ontodriver-jena/src/test/java/cz/cvut/kbss/ontodriver/jena/util/IdentifierGeneratorTest.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * JOPA - * Copyright (C) 2026 Czech Technical University in Prague - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3.0 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library. - */ -package cz.cvut.kbss.ontodriver.jena.util; - -import cz.cvut.kbss.ontodriver.exception.IdentifierGenerationException; -import cz.cvut.kbss.ontodriver.jena.connector.StorageConnector; -import cz.cvut.kbss.ontodriver.jena.environment.Generator; -import cz.cvut.kbss.ontodriver.util.Vocabulary; -import org.apache.jena.rdf.model.ResourceFactory; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -import java.net.URI; -import java.util.Collections; - -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anySet; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -@ExtendWith(MockitoExtension.class) -public class IdentifierGeneratorTest { - - private static final URI TYPE_URI = Generator.generateUri(); - - @Mock - private StorageConnector connectorMock; - - private IdentifierGenerator generator; - - @BeforeEach - public void setUp() { - this.generator = new IdentifierGenerator(connectorMock); - } - - @Test - public void generateIdentifierCreatesIdentifierBasedOnClassUri() { - final URI result = generator.generateIdentifier(TYPE_URI); - assertNotNull(result); - assertTrue(result.toString().contains(TYPE_URI.toString())); - } - - @Test - public void generateIdentifierChecksForExistenceOfNewlyGeneratedIdentifier() { - when(connectorMock.contains(any(), any(), any(), anySet())).thenReturn(false); - final URI result = generator.generateIdentifier(TYPE_URI); - assertNotNull(result); - verify(connectorMock) - .contains(ResourceFactory.createResource(result.toString()), ResourceFactory.createProperty( - Vocabulary.RDF_TYPE), ResourceFactory.createResource(TYPE_URI.toString()), - Collections.emptySet()); - } - - @Test - public void throwsIdentifierGenerationExceptionWhenMaximumAttemptsToGenerateAreExceeded() { - when(connectorMock.contains(any(), any(), any(), any())).thenReturn(true); - assertThrows(IdentifierGenerationException.class, () -> generator.generateIdentifier(TYPE_URI)); - } -} diff --git a/ontodriver-owlapi/pom.xml b/ontodriver-owlapi/pom.xml index 66cf424f1..92eaf4cae 100644 --- a/ontodriver-owlapi/pom.xml +++ b/ontodriver-owlapi/pom.xml @@ -11,7 +11,7 @@ cz.cvut.kbss.jopa jopa-all - 2.10.1 + 2.11.0 ../pom.xml diff --git a/ontodriver-owlapi/src/main/java/cz/cvut/kbss/ontodriver/owlapi/OwlapiAdapter.java b/ontodriver-owlapi/src/main/java/cz/cvut/kbss/ontodriver/owlapi/OwlapiAdapter.java index 2dabff194..a8daece22 100644 --- a/ontodriver-owlapi/src/main/java/cz/cvut/kbss/ontodriver/owlapi/OwlapiAdapter.java +++ b/ontodriver-owlapi/src/main/java/cz/cvut/kbss/ontodriver/owlapi/OwlapiAdapter.java @@ -30,7 +30,6 @@ import cz.cvut.kbss.ontodriver.owlapi.query.OwlapiPreparedStatement; import cz.cvut.kbss.ontodriver.owlapi.query.OwlapiStatement; import cz.cvut.kbss.ontodriver.owlapi.query.StatementExecutorFactory; -import cz.cvut.kbss.ontodriver.owlapi.util.IdentifierGenerator; import org.semanticweb.owlapi.model.OWLAxiom; import org.semanticweb.owlapi.model.OWLDataFactory; import org.semanticweb.owlapi.model.OWLOntology; @@ -182,11 +181,6 @@ public void persist(AxiomValueDescriptor descriptor) { new AxiomSaver(this, ontologySnapshot).persist(descriptor); } - public URI generateIdentifier(URI classUri) { - startTransactionIfNotActive(); - return new IdentifierGenerator(ontology()).generateIdentifier(classUri); - } - public void update(AxiomValueDescriptor descriptor) { startTransactionIfNotActive(); new EpistemicAxiomRemover(this, ontologySnapshot).remove(descriptor); diff --git a/ontodriver-owlapi/src/main/java/cz/cvut/kbss/ontodriver/owlapi/OwlapiConnection.java b/ontodriver-owlapi/src/main/java/cz/cvut/kbss/ontodriver/owlapi/OwlapiConnection.java index 59bade1b1..ec53ab40c 100644 --- a/ontodriver-owlapi/src/main/java/cz/cvut/kbss/ontodriver/owlapi/OwlapiConnection.java +++ b/ontodriver-owlapi/src/main/java/cz/cvut/kbss/ontodriver/owlapi/OwlapiConnection.java @@ -193,13 +193,6 @@ public void persist(AxiomValueDescriptor descriptor) throws OntoDriverException } } - @Override - public URI generateIdentifier(URI classUri) { - ensureOpen(); - Objects.requireNonNull(classUri); - return adapter.generateIdentifier(classUri); - } - @Override public void update(AxiomValueDescriptor descriptor) throws OntoDriverException { ensureOpen(); diff --git a/ontodriver-owlapi/src/main/java/cz/cvut/kbss/ontodriver/owlapi/change/SubjectPropertyRemove.java b/ontodriver-owlapi/src/main/java/cz/cvut/kbss/ontodriver/owlapi/change/SubjectPropertyRemove.java index 899bf35d0..30e42821c 100644 --- a/ontodriver-owlapi/src/main/java/cz/cvut/kbss/ontodriver/owlapi/change/SubjectPropertyRemove.java +++ b/ontodriver-owlapi/src/main/java/cz/cvut/kbss/ontodriver/owlapi/change/SubjectPropertyRemove.java @@ -35,10 +35,9 @@ public SubjectPropertyRemove(OWLNamedIndividual subject, T property) { @Override public boolean overrides(TransactionalChange existing) { - if (existing instanceof MutableAddAxiom ax) { - if (ax.getAxiom() instanceof OWLPropertyAssertionAxiom assertionAxiom) { - return subject.equals(assertionAxiom.getSubject()) && property.equals(assertionAxiom.getProperty()); - } + if (existing instanceof MutableAddAxiom ax + && ax.getAxiom() instanceof OWLPropertyAssertionAxiom assertionAxiom) { + return subject.equals(assertionAxiom.getSubject()) && property.equals(assertionAxiom.getProperty()); } return false; } diff --git a/ontodriver-owlapi/src/main/java/cz/cvut/kbss/ontodriver/owlapi/container/ContainerHandler.java b/ontodriver-owlapi/src/main/java/cz/cvut/kbss/ontodriver/owlapi/container/ContainerHandler.java index af45444f4..4e4cc7cbd 100644 --- a/ontodriver-owlapi/src/main/java/cz/cvut/kbss/ontodriver/owlapi/container/ContainerHandler.java +++ b/ontodriver-owlapi/src/main/java/cz/cvut/kbss/ontodriver/owlapi/container/ContainerHandler.java @@ -18,7 +18,6 @@ package cz.cvut.kbss.ontodriver.owlapi.container; import com.google.common.collect.Multimap; -import cz.cvut.kbss.jopa.vocabulary.RDFS; import cz.cvut.kbss.ontodriver.descriptor.ContainerDescriptor; import cz.cvut.kbss.ontodriver.descriptor.ContainerValueDescriptor; import cz.cvut.kbss.ontodriver.exception.IntegrityConstraintViolatedException; @@ -35,6 +34,7 @@ import cz.cvut.kbss.ontodriver.owlapi.exception.OwlapiDriverException; import cz.cvut.kbss.ontodriver.owlapi.list.ListContentStorageHelper; import cz.cvut.kbss.ontodriver.owlapi.util.OwlapiUtils; +import cz.cvut.kbss.ontodriver.util.IdentifierUtils; import org.semanticweb.owlapi.model.IRI; import org.semanticweb.owlapi.model.OWLAnnotation; import org.semanticweb.owlapi.model.OWLAnnotationValue; @@ -235,20 +235,29 @@ public void persistContainer(ContainerValueDescriptor descriptor) { final OWLNamedIndividual owner = dataFactory.getOWLNamedIndividual(IRI.create(descriptor.getOwner() .getIdentifier())); final List changes = new ArrayList<>(descriptor.getValues().size() + 1); - final OWLNamedIndividual container = createContainer(owner, descriptor.getProperty(), descriptor.getType(), changes); + final OWLNamedIndividual container = createContainer(owner, descriptor, changes); changes.addAll(createContainerContent(container, descriptor.getProperty(), descriptor.getValues())); owlapiAdapter.addTransactionalChanges(snapshot.applyChanges(changes)); } - private OWLNamedIndividual createContainer(OWLNamedIndividual owner, Assertion property, URI containerType, - List changes) { - final OWLNamedIndividual container = dataFactory.getOWLNamedIndividual(IRI.create(owlapiAdapter.generateIdentifier(URI.create(RDFS.CONTAINER)))); - final OWLObjectProperty containerOwnerProperty = dataFactory.getOWLObjectProperty(IRI.create(property.getIdentifier())); + private OWLNamedIndividual createContainer(OWLNamedIndividual owner, ContainerValueDescriptor descriptor, List changes) { + final OWLNamedIndividual container = dataFactory.getOWLNamedIndividual(generateContainerIri(descriptor)); + final OWLObjectProperty containerOwnerProperty = dataFactory.getOWLObjectProperty(IRI.create(descriptor.getProperty().getIdentifier())); changes.add(new MutableAddAxiom(ontology, dataFactory.getOWLObjectPropertyAssertionAxiom(containerOwnerProperty, owner, container))); - changes.add(new MutableAddAxiom(ontology, dataFactory.getOWLClassAssertionAxiom(dataFactory.getOWLClass(IRI.create(containerType)), container))); + changes.add(new MutableAddAxiom(ontology, dataFactory.getOWLClassAssertionAxiom(dataFactory.getOWLClass(IRI.create(descriptor.getType())), container))); return container; } + private IRI generateContainerIri(ContainerValueDescriptor descriptor) { + IRI iri; + do { + iri = IRI.create(descriptor.getOwner().getIdentifier() + .toString() + descriptor.getType().getFragment() + .toLowerCase() + "_" + IdentifierUtils.randomInt()); + } while (ontology.containsIndividualInSignature(iri)); + return iri; + } + private List createContainerContent(OWLIndividual container, Assertion property, List values) { diff --git a/ontodriver-owlapi/src/main/java/cz/cvut/kbss/ontodriver/owlapi/util/IdentifierGenerator.java b/ontodriver-owlapi/src/main/java/cz/cvut/kbss/ontodriver/owlapi/util/IdentifierGenerator.java deleted file mode 100644 index 7d757c90a..000000000 --- a/ontodriver-owlapi/src/main/java/cz/cvut/kbss/ontodriver/owlapi/util/IdentifierGenerator.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * JOPA - * Copyright (C) 2026 Czech Technical University in Prague - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3.0 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library. - */ -package cz.cvut.kbss.ontodriver.owlapi.util; - -import cz.cvut.kbss.ontodriver.exception.IdentifierGenerationException; -import cz.cvut.kbss.ontodriver.util.IdentifierUtils; -import org.semanticweb.owlapi.model.IRI; -import org.semanticweb.owlapi.model.OWLOntology; - -import java.net.URI; - -public class IdentifierGenerator { - - private static final int GENERATION_THRESHOLD = 100; - - private final OWLOntology ontology; - - public IdentifierGenerator(OWLOntology ontology) { - assert ontology != null; - this.ontology = ontology; - } - - /** - * Generates an identifier which is unique w.r.t. individuals in the known ontology. - * - * @param classUri URI of individual's class, used as base for the identifier - * @return Unique identifier - * @throws IdentifierGenerationException If unable to generate unique identifier - */ - public URI generateIdentifier(URI classUri) { - boolean unique = false; - URI id = null; - int counter = 0; - while (!unique && counter++ < GENERATION_THRESHOLD) { - id = IdentifierUtils.generateIdentifier(classUri); - unique = isIdentifierUnique(id); - } - if (!unique) { - throw new IdentifierGenerationException("Unable to generate a unique identifier."); - } - return id; - } - - private boolean isIdentifierUnique(URI uri) { - return !ontology.containsIndividualInSignature(IRI.create(uri)); - } -} diff --git a/ontodriver-owlapi/src/test/java/cz/cvut/kbss/ontodriver/owlapi/OwlapiConnectionTest.java b/ontodriver-owlapi/src/test/java/cz/cvut/kbss/ontodriver/owlapi/OwlapiConnectionTest.java index 7821fe5cf..2b1398348 100644 --- a/ontodriver-owlapi/src/test/java/cz/cvut/kbss/ontodriver/owlapi/OwlapiConnectionTest.java +++ b/ontodriver-owlapi/src/test/java/cz/cvut/kbss/ontodriver/owlapi/OwlapiConnectionTest.java @@ -44,9 +44,7 @@ import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.Mockito.any; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -153,22 +151,6 @@ public void typesReturnsTypesHandlerForOwlapiDriver() { assertEquals(types, result); } - @Test - public void generateIdentifierGetsNewIdentifier() { - final URI identifier = URI.create("http://newIdentifier"); - when(adapterMock.generateIdentifier(any(URI.class))).thenReturn(identifier); - - final URI result = connection.generateIdentifier(URI.create("http://baseUri")); - assertEquals(identifier, result); - } - - @Test - public void generateIdentifierOnClosedThrowsException() { - connection.close(); - assertThrows(IllegalStateException.class, () -> connection.generateIdentifier(URI.create("http://baseUri"))); - verify(adapterMock, never()).generateIdentifier(any(URI.class)); - } - @Test public void propertiesReturnPropertiesHandlerForOwlapiDriver() { final OwlapiProperties properties = mock(OwlapiProperties.class); diff --git a/ontodriver-owlapi/src/test/java/cz/cvut/kbss/ontodriver/owlapi/container/ContainerHandlerTest.java b/ontodriver-owlapi/src/test/java/cz/cvut/kbss/ontodriver/owlapi/container/ContainerHandlerTest.java index 5fae18d47..b80aa8b6d 100644 --- a/ontodriver-owlapi/src/test/java/cz/cvut/kbss/ontodriver/owlapi/container/ContainerHandlerTest.java +++ b/ontodriver-owlapi/src/test/java/cz/cvut/kbss/ontodriver/owlapi/container/ContainerHandlerTest.java @@ -59,8 +59,6 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) class ContainerHandlerTest { @@ -205,7 +203,6 @@ void persistContainerCreatesContainerAndAddsSpecifiedValuesToIt() { final List values = List.of(NamedResource.create(Generator.generateUri()), NamedResource.create(Generator.generateUri())); final ContainerValueDescriptor descriptor = ContainerValueDescriptor.bagValueDescriptor(owner, property); values.forEach(descriptor::addValue); - when(owlapiAdapter.generateIdentifier(any())).thenReturn(Generator.generateUri()); sut.persistContainer(descriptor); verifyContainerContent(property, RDF.BAG, values); @@ -319,7 +316,6 @@ void persistContainerSupportsTranslationsAsContent() { new Translations(Map.of("en", "Two", "cs", "Dva"))); final ContainerValueDescriptor descriptor = ContainerValueDescriptor.bagValueDescriptor(owner, property); values.forEach(descriptor::addValue); - when(owlapiAdapter.generateIdentifier(any())).thenReturn(Generator.generateUri()); sut.persistContainer(descriptor); diff --git a/ontodriver-owlapi/src/test/java/cz/cvut/kbss/ontodriver/owlapi/util/IdentifierGeneratorTest.java b/ontodriver-owlapi/src/test/java/cz/cvut/kbss/ontodriver/owlapi/util/IdentifierGeneratorTest.java deleted file mode 100644 index 87ca81118..000000000 --- a/ontodriver-owlapi/src/test/java/cz/cvut/kbss/ontodriver/owlapi/util/IdentifierGeneratorTest.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * JOPA - * Copyright (C) 2026 Czech Technical University in Prague - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3.0 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library. - */ -package cz.cvut.kbss.ontodriver.owlapi.util; - -import cz.cvut.kbss.ontodriver.exception.IdentifierGenerationException; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.semanticweb.owlapi.model.IRI; -import org.semanticweb.owlapi.model.OWLOntology; - -import java.lang.reflect.Field; -import java.net.URI; -import java.util.HashSet; -import java.util.Set; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -@ExtendWith(MockitoExtension.class) -class IdentifierGeneratorTest { - - @Mock - private OWLOntology ontologyMock; - - private IdentifierGenerator generator; - - @BeforeEach - void setUp() { - this.generator = new IdentifierGenerator(ontologyMock); - } - - @Test - void generatesUniqueIdentifier() { - final URI baseUri = URI.create("http://baseUri/"); - when(ontologyMock.containsIndividualInSignature(any(IRI.class))).thenReturn(false); - final Set results = new HashSet<>(); - int tries = 100; - for (int i = 0; i < tries; i++) { - results.add(generator.generateIdentifier(baseUri)); - } - results.forEach(uri -> assertTrue(uri.toString().contains(baseUri.toString()))); - assertEquals(tries, results.size()); // All tries lead to unique URI - } - - @Test - void throwsExceptionWhenUnableToGenerateUriWithinThreshold() throws Exception { - final URI baseUri = URI.create("http://baseUri/"); - final int threshold = extractThreshold(); - when(ontologyMock.containsIndividualInSignature(any(IRI.class))).thenReturn(true); - try { - assertThrows(IdentifierGenerationException.class, () -> generator.generateIdentifier(baseUri)); - } finally { - verify(ontologyMock, times(threshold)).containsIndividualInSignature(any(IRI.class)); - } - } - - private int extractThreshold() throws Exception { - final Field thresholdField = IdentifierGenerator.class.getDeclaredField("GENERATION_THRESHOLD"); - thresholdField.setAccessible(true); - return (int) thresholdField.get(null); - } -} diff --git a/ontodriver-rdf4j/pom.xml b/ontodriver-rdf4j/pom.xml index f33f27bf9..c8b93519c 100644 --- a/ontodriver-rdf4j/pom.xml +++ b/ontodriver-rdf4j/pom.xml @@ -10,7 +10,7 @@ jopa-all cz.cvut.kbss.jopa - 2.10.1 + 2.11.0 diff --git a/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/Rdf4jAdapter.java b/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/Rdf4jAdapter.java index 9505fd6ec..75225aea9 100644 --- a/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/Rdf4jAdapter.java +++ b/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/Rdf4jAdapter.java @@ -22,7 +22,6 @@ import cz.cvut.kbss.ontodriver.Wrapper; import cz.cvut.kbss.ontodriver.descriptor.AxiomDescriptor; import cz.cvut.kbss.ontodriver.descriptor.AxiomValueDescriptor; -import cz.cvut.kbss.ontodriver.exception.IdentifierGenerationException; import cz.cvut.kbss.ontodriver.exception.OntoDriverException; import cz.cvut.kbss.ontodriver.model.Axiom; import cz.cvut.kbss.ontodriver.rdf4j.config.Constants; @@ -35,30 +34,22 @@ import cz.cvut.kbss.ontodriver.rdf4j.list.SimpleListHandler; import cz.cvut.kbss.ontodriver.rdf4j.query.QuerySpecification; import cz.cvut.kbss.ontodriver.rdf4j.util.Rdf4jUtils; -import cz.cvut.kbss.ontodriver.util.IdentifierUtils; import cz.cvut.kbss.ontodriver.util.Transaction; import org.eclipse.rdf4j.model.Resource; import org.eclipse.rdf4j.model.Statement; import org.eclipse.rdf4j.model.Value; import org.eclipse.rdf4j.model.ValueFactory; -import org.eclipse.rdf4j.model.vocabulary.RDF; import org.eclipse.rdf4j.query.TupleQueryResult; import java.net.URI; import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.List; import java.util.Set; import java.util.stream.Collectors; public class Rdf4jAdapter implements StatementExecutor, Closeable, Wrapper { - /** - * Maximum number of attempts to generate a unique identifier - */ - private static final int ID_GENERATION_THRESHOLD = 64; - private final RepoConnection connector; private final ValueFactory valueFactory; private final RuntimeConfiguration config; @@ -143,22 +134,6 @@ List getContexts() throws Rdf4jDriverException { return contexts; } - URI generateIdentifier(URI classUri) throws Rdf4jDriverException { - startTransactionIfNotActive(); - boolean unique = false; - URI id = null; - int counter = 0; - while (!unique && counter++ < ID_GENERATION_THRESHOLD) { - id = IdentifierUtils.generateIdentifier(classUri); - unique = isIdentifierUnique(id, classUri); - } - if (!unique) { - throw new IdentifierGenerationException("Unable to generate a unique identifier."); - } - return id; - - } - private void startTransactionIfNotActive() throws Rdf4jDriverException { if (!transaction.isActive()) { connector.begin(); @@ -166,12 +141,6 @@ private void startTransactionIfNotActive() throws Rdf4jDriverException { } } - private boolean isIdentifierUnique(URI identifier, URI classUri) throws Rdf4jDriverException { - return !connector.containsStatement( - Rdf4jUtils.toRdf4jIri(identifier, valueFactory), RDF.TYPE, - Rdf4jUtils.toRdf4jIri(classUri, valueFactory), true, Collections.emptySet()); - } - boolean contains(Axiom axiom, Set contexts) throws Rdf4jDriverException { startTransactionIfNotActive(); final Value value = axiomObjectToRdf4jValue(axiom); @@ -225,10 +194,6 @@ void remove(AxiomDescriptor axiomDescriptor) throws Rdf4jDriverException { new EpistemicAxiomRemover(connector, valueFactory).remove(axiomDescriptor); } - StatementExecutor getQueryExecutor() { - return connector; - } - SimpleListHandler getSimpleListHandler() throws Rdf4jDriverException { startTransactionIfNotActive(); return new SimpleListHandler(connector, valueFactory); diff --git a/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/Rdf4jConnection.java b/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/Rdf4jConnection.java index f84d6974c..d2f7f674d 100644 --- a/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/Rdf4jConnection.java +++ b/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/Rdf4jConnection.java @@ -27,7 +27,6 @@ import cz.cvut.kbss.ontodriver.Types; import cz.cvut.kbss.ontodriver.descriptor.AxiomDescriptor; import cz.cvut.kbss.ontodriver.descriptor.AxiomValueDescriptor; -import cz.cvut.kbss.ontodriver.exception.IdentifierGenerationException; import cz.cvut.kbss.ontodriver.exception.OntoDriverException; import cz.cvut.kbss.ontodriver.model.Axiom; import cz.cvut.kbss.ontodriver.rdf4j.exception.Rdf4jDriverException; @@ -174,17 +173,6 @@ public List getContexts() throws OntoDriverException { return adapter.getContexts(); } - @Override - public URI generateIdentifier(URI classUri) throws OntoDriverException { - ensureOpen(); - Objects.requireNonNull(classUri); - try { - return adapter.generateIdentifier(classUri); - } catch (IdentifierGenerationException e) { - throw new Rdf4jDriverException(e); - } - } - @Override public boolean contains(Axiom axiom, Set contexts) throws OntoDriverException { ensureOpen(); diff --git a/ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/Rdf4jAdapterTest.java b/ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/Rdf4jAdapterTest.java index a279408a1..489a443e9 100644 --- a/ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/Rdf4jAdapterTest.java +++ b/ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/Rdf4jAdapterTest.java @@ -21,7 +21,6 @@ import cz.cvut.kbss.ontodriver.config.DriverConfiguration; import cz.cvut.kbss.ontodriver.descriptor.AxiomDescriptor; import cz.cvut.kbss.ontodriver.descriptor.AxiomValueDescriptor; -import cz.cvut.kbss.ontodriver.exception.IdentifierGenerationException; import cz.cvut.kbss.ontodriver.model.Assertion; import cz.cvut.kbss.ontodriver.model.Axiom; import cz.cvut.kbss.ontodriver.model.AxiomImpl; @@ -65,10 +64,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertSame; -import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyBoolean; @@ -568,51 +565,6 @@ void testFindEntityWithBlankNodeInTypes() throws Exception { assertEquals(type, ax.getValue().stringValue()); } - @Test - void testGenerateIdentifier_ClassWithHash() throws Exception { - final URI clsUri = URI.create("http://someClass.cz#class"); - when(connectorMock.containsStatement(any(Resource.class), eq(RDF.TYPE), - eq(VF.createIRI(clsUri.toString())), eq(true), eq(Collections.emptySet()))).thenReturn(false); - final URI res = adapter.generateIdentifier(clsUri); - assertNotNull(res); - assertTrue(res.toString().contains(clsUri.toString())); - verify(connectorMock).containsStatement(VF.createIRI(res.toString()), RDF.TYPE, - VF.createIRI(clsUri.toString()), true, Collections.emptySet()); - } - - @Test - void testGenerateIdentifier_ClassWithoutHash() throws Exception { - final URI clsUri = URI.create("http://someClass.cz/class"); - when(connectorMock.containsStatement(any(Resource.class), eq(RDF.TYPE), - eq(VF.createIRI(clsUri.toString())), eq(true), eq(Collections.emptySet()))).thenReturn(false); - final URI res = adapter.generateIdentifier(clsUri); - assertNotNull(res); - assertTrue(res.toString().contains(clsUri.toString())); - assertTrue(res.toString().contains("/instance")); - verify(connectorMock).containsStatement(VF.createIRI(res.toString()), RDF.TYPE, - VF.createIRI(clsUri.toString()), true, Collections.emptySet()); - } - - @Test - void testGenerateIdentifier_ClassEndsWithSlash() throws Exception { - final URI clsUri = URI.create("http://someClass.cz/class/"); - when(connectorMock.containsStatement(any(Resource.class), eq(RDF.TYPE), - eq(VF.createIRI(clsUri.toString())), eq(true), eq(Collections.emptySet()))).thenReturn(false); - final URI res = adapter.generateIdentifier(clsUri); - assertNotNull(res); - assertTrue(res.toString().contains(clsUri.toString())); - verify(connectorMock).containsStatement(VF.createIRI(res.toString()), RDF.TYPE, - VF.createIRI(clsUri.toString()), true, Collections.emptySet()); - } - - @Test - void testGenerateIdentifierNeverUnique() throws Exception { - final URI clsUri = URI.create("http://someClass.cz#class"); - when(connectorMock.containsStatement(any(Resource.class), eq(RDF.TYPE), - eq(VF.createIRI(clsUri.toString())), eq(true), eq(Collections.emptySet()))).thenReturn(true); - assertThrows(IdentifierGenerationException.class, () -> adapter.generateIdentifier(clsUri)); - } - @Test void testRemove() throws Exception { final AxiomDescriptor desc = new AxiomDescriptor(SUBJECT); diff --git a/ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/Rdf4jConnectionTest.java b/ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/Rdf4jConnectionTest.java index 47e6a246c..65e7a156d 100644 --- a/ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/Rdf4jConnectionTest.java +++ b/ontodriver-rdf4j/src/test/java/cz/cvut/kbss/ontodriver/rdf4j/Rdf4jConnectionTest.java @@ -23,7 +23,6 @@ import cz.cvut.kbss.ontodriver.descriptor.AxiomDescriptor; import cz.cvut.kbss.ontodriver.descriptor.AxiomValueDescriptor; import cz.cvut.kbss.ontodriver.model.Axiom; -import cz.cvut.kbss.ontodriver.rdf4j.environment.Generator; import cz.cvut.kbss.ontodriver.rdf4j.query.Rdf4jPreparedStatement; import cz.cvut.kbss.ontodriver.rdf4j.query.Rdf4jStatement; import org.junit.jupiter.api.BeforeEach; @@ -33,10 +32,26 @@ import org.mockito.junit.jupiter.MockitoExtension; import java.net.URI; -import java.util.*; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anySet; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) public class Rdf4jConnectionTest { @@ -149,16 +164,6 @@ public void testGetContextsOnClosed() throws Exception { verify(adapterMock, never()).getContexts(); } - @Test - public void testGenerateIdentifier() throws Exception { - final URI classUri = URI.create("https://onto.fel.cvut.cz/ontologies/jopa/owlClassA"); - final URI identifier = Generator.generateUri(); - when(adapterMock.generateIdentifier(classUri)).thenReturn(identifier); - final URI res = connection.generateIdentifier(classUri); - assertEquals(identifier, res); - verify(adapterMock).generateIdentifier(classUri); - } - @Test public void testContainsAxiom() throws Exception { final Axiom ax = mock(Axiom.class); diff --git a/ontodriver-virtuoso/pom.xml b/ontodriver-virtuoso/pom.xml index 3471e3294..98b4f0c2f 100644 --- a/ontodriver-virtuoso/pom.xml +++ b/ontodriver-virtuoso/pom.xml @@ -6,7 +6,7 @@ cz.cvut.kbss.jopa jopa-all - 2.10.1 + 2.11.0 ontodriver-virtuoso diff --git a/pom.xml b/pom.xml index 6fba3aea2..a72ba96f1 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 cz.cvut.kbss.jopa - 2.10.1 + 2.11.0 jopa-all pom JOPA @@ -128,6 +128,15 @@ maven-javadoc-plugin ${maven.javadoc.plugin.version} + + + + implNote + a + Implementation Note: + + + attach-javadocs @@ -175,6 +184,15 @@ maven-javadoc-plugin ${maven.javadoc.plugin.version} + + + + implNote + a + Implementation Note: + + + attach-javadocs @@ -195,7 +213,7 @@ org.jacoco jacoco-maven-plugin - 0.8.13 + 0.8.15