diff --git a/CHANGELOG.md b/CHANGELOG.md index 17c7c8b6a..e82d1ef3b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # JOPA - Change Log +## 1.1.1 - 2023-08-23 +- Fix a possible deadlock in case RDF4J's HTTP client connection pool is exhausted by concurrent requests (Bug #191). +- Introduce a marker `@Property` annotation used on all OWL property mapping annotations. +- Dependency updates: AspectJ 1.9.20, RDF4J 4.3.5. + ## 1.1.0 - 2023-07-28 - Add support for MEMBER OF in SOQL/Criteria API (Enhancement #176). - Support language matching in SOQL/Criteria API (Enhancement #161). diff --git a/datatype/pom.xml b/datatype/pom.xml index 9ca73b822..d7cc9213c 100644 --- a/datatype/pom.xml +++ b/datatype/pom.xml @@ -6,7 +6,7 @@ jopa-all cz.cvut.kbss.jopa - 1.1.0 + 1.1.1 ../pom.xml diff --git a/datatype/src/test/java/cz/cvut/kbss/jopa/datatype/xsd/XsdDatatypeMapperTest.java b/datatype/src/test/java/cz/cvut/kbss/jopa/datatype/xsd/XsdDatatypeMapperTest.java index e759dda24..3b793848d 100644 --- a/datatype/src/test/java/cz/cvut/kbss/jopa/datatype/xsd/XsdDatatypeMapperTest.java +++ b/datatype/src/test/java/cz/cvut/kbss/jopa/datatype/xsd/XsdDatatypeMapperTest.java @@ -22,6 +22,7 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; +import java.math.BigDecimal; import java.math.BigInteger; import java.net.URI; import java.time.*; @@ -35,17 +36,43 @@ class XsdDatatypeMapperTest { private final XsdDatatypeMapper sut = XsdDatatypeMapper.getInstance(); - @Test - void mapReturnsUnsignedByteAsShort() { - final byte value = 115; - final Literal literal = Literal.from(Byte.toString(value), XSD.UNSIGNED_BYTE); - assertEquals((short) value, map(literal)); - } - - private Object map(Literal literal) { + @ParameterizedTest + @MethodSource("mapTestArguments") + void mapCorrectlyConvertsValues(Object expected, Literal literal) { final Optional result = sut.map(literal); assertTrue(result.isPresent()); - return result.get(); + assertEquals(expected, result.get()); + } + + static Stream mapTestArguments() { + final long longValue = System.currentTimeMillis(); + final LocalDate date = LocalDate.now(); + final OffsetTime time = OffsetTime.of(12, 45, 15, 0, ZoneOffset.UTC); + final OffsetDateTime dateTime = OffsetDateTime.of(2021, 11, 17, 12, 23, 10, 0, ZoneOffset.UTC); + final Duration duration = Duration.ofHours(24).plusMinutes(15).plusSeconds(44); + return Stream.of( + Arguments.of(true, Literal.from(Boolean.toString(true), XSD.BOOLEAN)), + Arguments.of((byte) 101, Literal.from(Byte.toString((byte) 101), XSD.BYTE)), + Arguments.of((short) 115, Literal.from(Byte.toString((byte) 115), XSD.UNSIGNED_BYTE)), + Arguments.of(1234, Literal.from(Short.toString((short) 1234), XSD.UNSIGNED_SHORT)), + Arguments.of((long) Integer.MAX_VALUE, Literal.from(Integer.toString(Integer.MAX_VALUE), XSD.UNSIGNED_INT)), + Arguments.of(BigInteger.valueOf(Integer.MIN_VALUE), Literal.from(Integer.toString(Integer.MIN_VALUE), XSD.INTEGER)), + Arguments.of(BigInteger.valueOf(longValue), Literal.from(Long.toString(longValue), XSD.UNSIGNED_LONG)), + Arguments.of("test", Literal.from("test", XSD.STRING)), + Arguments.of("test", Literal.from("test", XSD.NORMALIZED_STRING)), + Arguments.of(date,Literal.from(date.format(DateTimeFormatter.ISO_DATE), XSD.DATE) ), + Arguments.of(time, Literal.from(time.format(DateTimeFormatter.ISO_TIME), XSD.TIME)), + Arguments.of(dateTime, Literal.from(dateTime.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME), XSD.DATETIME)), + Arguments.of(duration, Literal.from(duration.toString(), XSD.DURATION)), + Arguments.of(URI.create("https://www.w3.org/TR/xmlschema-2/#anyURI"), Literal.from("https://www.w3.org/TR/xmlschema-2/#anyURI", XSD.ANY_URI)), + Arguments.of(new BigDecimal("3.141952"), Literal.from("3.141952", XSD.DECIMAL)), + Arguments.of(Float.NaN, Literal.from("NaN", XSD.FLOAT)), + Arguments.of(Float.NEGATIVE_INFINITY, Literal.from("-INF", XSD.FLOAT)), + Arguments.of(Float.POSITIVE_INFINITY, Literal.from("INF", XSD.FLOAT)), + Arguments.of(Double.NaN, Literal.from("NaN", XSD.DOUBLE)), + Arguments.of(Double.NEGATIVE_INFINITY, Literal.from("-INF", XSD.DOUBLE)), + Arguments.of(Double.POSITIVE_INFINITY, Literal.from("INF", XSD.DOUBLE)) + ); } @Test @@ -54,103 +81,9 @@ void mapReturnsEmptyOptionalForUnknownDatatype() { assertFalse(sut.map(Literal.from("jpeg", datatype)).isPresent()); } - @Test - void mapReturnsUnsignedShortAsInt() { - final short value = 1234; - final Literal literal = Literal.from(Short.toString(value), XSD.UNSIGNED_SHORT); - assertEquals((int) value, map(literal)); - } - @Test void mapThrowsDatatypeMappingExceptionForInvalidLiteralValue() { final Literal invalidValue = Literal.from("abcd", XSD.INT); assertThrows(DatatypeMappingException.class, () -> sut.map(invalidValue)); } - - @Test - void mapReturnsUnsignedIntAsLong() { - final int value = Integer.MAX_VALUE; - final Literal literal = Literal.from(Integer.toString(value), XSD.UNSIGNED_INT); - assertEquals((long) value, map(literal)); - } - - @Test - void mapReturnsIntegerAsBigInteger() { - final int value = Integer.MIN_VALUE; - final Literal literal = Literal.from(Integer.toString(value), XSD.INTEGER); - assertEquals(BigInteger.valueOf(value), map(literal)); - } - - @Test - void mapReturnsUnsignedLongAsBigInteger() { - final long value = System.currentTimeMillis(); - final Literal literal = Literal.from(Long.toString(value), XSD.UNSIGNED_LONG); - assertEquals(BigInteger.valueOf(value), map(literal)); - } - - @Test - void mapReturnsXsdStringAsString() { - final String value = "test"; - final Literal literal = Literal.from(value, XSD.STRING); - assertEquals(value, map(literal)); - } - - @Test - void mapReturnsXsdNormalizedStringAsString() { - final String value = "test"; - final Literal literal = Literal.from(value, XSD.NORMALIZED_STRING); - assertEquals(value, map(literal)); - } - - @Test - void mapReturnsLocalDateForXsdDate() { - final LocalDate date = LocalDate.now(); - final Literal literal = Literal.from(date.format(DateTimeFormatter.ISO_DATE), XSD.DATE); - assertEquals(date, map(literal)); - } - - @Test - void mapReturnsOffsetTimeForXsdTime() { - final OffsetTime time = OffsetTime.of(12, 45, 15, 0, ZoneOffset.UTC); - assertEquals(time, map(Literal.from(time.format(DateTimeFormatter.ISO_TIME), XSD.TIME))); - } - - @Test - void mapReturnsOffsetDateTimeForXsdDateTime() { - final OffsetDateTime dateTime = OffsetDateTime.of(2021, 11, 17, 12, 23, 10, 0, ZoneOffset.UTC); - assertEquals(dateTime, - map(Literal.from(dateTime.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME), XSD.DATETIME))); - } - - @Test - void mapReturnsDurationForXsdDuration() { - final Duration duration = Duration.ofHours(24).plusMinutes(15).plusSeconds(44); - assertEquals(duration, map(Literal.from(duration.toString(), XSD.DURATION))); - } - - @Test - void mapReturnsUriForXsdAnyUri() { - final String uri = "https://www.w3.org/TR/xmlschema-2/#anyURI"; - assertEquals(URI.create(uri), map(Literal.from(uri, XSD.ANY_URI))); - } - - /** - * Bug #108 - */ - @ParameterizedTest - @MethodSource("floatingPointSpecialValuesGenerator") - void mapSupportsSpecialFloatingPointValuesMapping(Literal literal, Number expected) { - assertEquals(expected, map(literal)); - } - - private static Stream floatingPointSpecialValuesGenerator() { - return Stream.of( - Arguments.of(Literal.from("NaN", XSD.FLOAT), Float.NaN), - Arguments.of(Literal.from("-INF", XSD.FLOAT), Float.NEGATIVE_INFINITY), - Arguments.of(Literal.from("INF", XSD.FLOAT), Float.POSITIVE_INFINITY), - Arguments.of(Literal.from("NaN", XSD.DOUBLE), Double.NaN), - Arguments.of(Literal.from("-INF", XSD.DOUBLE), Double.NEGATIVE_INFINITY), - Arguments.of(Literal.from("INF", XSD.DOUBLE), Double.POSITIVE_INFINITY) - ); - } } diff --git a/jopa-api/pom.xml b/jopa-api/pom.xml index d5d8c52b8..aa73c8ac5 100644 --- a/jopa-api/pom.xml +++ b/jopa-api/pom.xml @@ -6,7 +6,7 @@ cz.cvut.kbss.jopa jopa-all - 1.1.0 + 1.1.1 ../pom.xml diff --git a/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/DefaultPersistenceProviderResolver.java b/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/DefaultPersistenceProviderResolver.java index 952c609e4..c42e113d7 100644 --- a/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/DefaultPersistenceProviderResolver.java +++ b/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/DefaultPersistenceProviderResolver.java @@ -22,7 +22,13 @@ import java.lang.ref.WeakReference; import java.security.AccessController; import java.security.PrivilegedAction; -import java.util.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Optional; +import java.util.ServiceConfigurationError; +import java.util.ServiceLoader; /** * Default implementation of the {@link PersistenceProviderResolver}, threadsafe. @@ -63,14 +69,7 @@ public List getPersistenceProviders() { List loadedProviders = new ArrayList<>(); Iterator ipp = ServiceLoader.load(PersistenceProvider.class, loader).iterator(); try { - while (ipp.hasNext()) { - try { - PersistenceProvider pp = ipp.next(); - loadedProviders.add(pp); - } catch (ServiceConfigurationError sce) { - LOG.warn("Unable to load PersistenceProvider implementation via service loader.", sce); - } - } + loadProvider(ipp).ifPresent(loadedProviders::add); } catch (ServiceConfigurationError sce) { LOG.warn("Unable to load PersistenceProvider implementation via service loader.", sce); } @@ -86,6 +85,15 @@ public List getPersistenceProviders() { return loadedProviders; } + private Optional loadProvider(Iterator ipp) { + try { + return Optional.of(ipp.next()); + } catch (ServiceConfigurationError sce) { + LOG.warn("Unable to load PersistenceProvider implementation via service loader.", sce); + return Optional.empty(); + } + } + /** * Remove garbage collected cache keys & providers. */ @@ -103,7 +111,8 @@ private static ClassLoader getContextClassLoader() { if (System.getSecurityManager() == null) { return Thread.currentThread().getContextClassLoader(); } else { - return AccessController.doPrivileged((PrivilegedAction) () -> Thread.currentThread().getContextClassLoader()); + return AccessController.doPrivileged((PrivilegedAction) () -> Thread.currentThread() + .getContextClassLoader()); } } @@ -192,7 +201,7 @@ public Object clone() { return clone; } catch (CloneNotSupportedException e) { // this should never happen - throw new InternalError(); + throw new InternalError(e); } } diff --git a/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/annotations/OWLAnnotationProperty.java b/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/annotations/OWLAnnotationProperty.java index a722378d2..bf8d74b78 100644 --- a/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/annotations/OWLAnnotationProperty.java +++ b/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/annotations/OWLAnnotationProperty.java @@ -14,7 +14,13 @@ */ package cz.cvut.kbss.jopa.model.annotations; -import java.lang.annotation.*; +import cz.cvut.kbss.jopa.model.annotations.util.Property; + +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; /** * Marks an attribute mapped to an OWL annotation property. @@ -22,6 +28,7 @@ * This means that the attribute can contain a literal or a reference to another object. */ @Documented +@Property @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD,ElementType.METHOD}) public @interface OWLAnnotationProperty { diff --git a/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/annotations/OWLDataProperty.java b/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/annotations/OWLDataProperty.java index 80ad00857..baf800304 100644 --- a/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/annotations/OWLDataProperty.java +++ b/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/annotations/OWLDataProperty.java @@ -14,7 +14,13 @@ */ package cz.cvut.kbss.jopa.model.annotations; -import java.lang.annotation.*; +import cz.cvut.kbss.jopa.model.annotations.util.Property; + +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; /** * Marks an attribute mapped to an OWL datatype property. @@ -24,6 +30,7 @@ * Note that for use with RDF(S), attributes annotated with this annotation are expected to reference literals. */ @Documented +@Property @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD,ElementType.METHOD}) public @interface OWLDataProperty{ diff --git a/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/annotations/OWLObjectProperty.java b/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/annotations/OWLObjectProperty.java index cd754e444..68f5bfd41 100644 --- a/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/annotations/OWLObjectProperty.java +++ b/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/annotations/OWLObjectProperty.java @@ -14,7 +14,13 @@ */ package cz.cvut.kbss.jopa.model.annotations; -import java.lang.annotation.*; +import cz.cvut.kbss.jopa.model.annotations.util.Property; + +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; /** * Marks an attribute mapped to an OWL object property. @@ -24,6 +30,7 @@ * Note that for use with RDF(S), attributes annotated with this annotation are expected to reference other RDF resources. */ @Documented +@Property @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD,ElementType.METHOD}) public @interface OWLObjectProperty { diff --git a/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/annotations/util/Property.java b/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/annotations/util/Property.java new file mode 100644 index 000000000..33fc79062 --- /dev/null +++ b/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/annotations/util/Property.java @@ -0,0 +1,14 @@ +package cz.cvut.kbss.jopa.model.annotations.util; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marks annotations that represent property mapping. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.ANNOTATION_TYPE) +public @interface Property { +} diff --git a/jopa-api/src/test/java/cz/cvut/kbss/jopa/model/util/PropertyTest.java b/jopa-api/src/test/java/cz/cvut/kbss/jopa/model/util/PropertyTest.java new file mode 100644 index 000000000..29f7cec99 --- /dev/null +++ b/jopa-api/src/test/java/cz/cvut/kbss/jopa/model/util/PropertyTest.java @@ -0,0 +1,19 @@ +package cz.cvut.kbss.jopa.model.util; + +import cz.cvut.kbss.jopa.model.annotations.OWLAnnotationProperty; +import cz.cvut.kbss.jopa.model.annotations.OWLDataProperty; +import cz.cvut.kbss.jopa.model.annotations.OWLObjectProperty; +import cz.cvut.kbss.jopa.model.annotations.util.Property; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class PropertyTest { + + @Test + void owlPropertiesAreMarkedWithPropertyAnnotation() { + assertTrue(OWLAnnotationProperty.class.isAnnotationPresent(Property.class)); + assertTrue(OWLDataProperty.class.isAnnotationPresent(Property.class)); + assertTrue(OWLObjectProperty.class.isAnnotationPresent(Property.class)); + } +} diff --git a/jopa-distribution/pom.xml b/jopa-distribution/pom.xml index 45447aac2..b5bdd81c7 100644 --- a/jopa-distribution/pom.xml +++ b/jopa-distribution/pom.xml @@ -6,7 +6,7 @@ cz.cvut.kbss.jopa jopa-all - 1.1.0 + 1.1.1 ../pom.xml diff --git a/jopa-impl/pom.xml b/jopa-impl/pom.xml index 6ddaf75a1..75d777612 100644 --- a/jopa-impl/pom.xml +++ b/jopa-impl/pom.xml @@ -6,7 +6,7 @@ cz.cvut.kbss.jopa jopa-all - 1.1.0 + 1.1.1 ../pom.xml diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/loaders/DefaultClasspathScanner.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/loaders/DefaultClasspathScanner.java index 66db6f527..50ecbfff7 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/loaders/DefaultClasspathScanner.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/loaders/DefaultClasspathScanner.java @@ -159,7 +159,7 @@ protected void processClass(String className) { try { final Class cls = Class.forName(className, true, classLoader); listeners.forEach(listener -> listener.accept(cls)); - } catch (Throwable e) { + } catch (Exception | NoClassDefFoundError e) { LOG.debug("Unable to load class {}, got error {}: {}. Skipping the class. If it is an entity class, ensure it is available on classpath and is built with supported Java version.", className, e.getClass() .getName(), e.getMessage()); } 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 4173da98a..29eb4552e 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 @@ -100,25 +100,33 @@ public Expression length(Expression x) { @Override public ParameterExpression parameter(Class paramClass) { - if (paramClass == null) throw new IllegalArgumentException("Class must be defined."); + if (paramClass == null) { + throw new IllegalArgumentException("Class must be defined."); + } return new ParameterExpressionImpl<>(paramClass, null, this); } @Override public ParameterExpression parameter(Class paramClass, String name) { - if (paramClass == null) throw new IllegalArgumentException("Class must be defined."); + if (paramClass == null) { + throw new IllegalArgumentException("Class must be defined."); + } return new ParameterExpressionImpl<>(paramClass, name, this); } @Override public Expression literal(T value) { - if (value == null) throw new IllegalArgumentException("Literal cannot be null."); + if (value == null) { + throw new IllegalArgumentException("Literal cannot be null."); + } return new ExpressionLiteralImpl<>(value, this); } @Override public Expression literal(String value, String languageTag) { - if (value == null) throw new IllegalArgumentException("Literal cannot be null."); + if (value == null) { + throw new IllegalArgumentException("Literal cannot be null."); + } return new ExpressionLiteralImpl<>(value, languageTag, this); } @@ -158,8 +166,11 @@ public Predicate and(Expression x, Expression y) { @Override public Predicate and(Predicate... restrictions) { - if (restrictions.length == 1) return new SimplePredicateImpl(restrictions[0], this); - else return new CompoundedPredicateImpl(Predicate.BooleanOperator.AND, Arrays.asList(restrictions), this); + if (restrictions.length == 1) { + return new SimplePredicateImpl(restrictions[0], this); + } else { + return new CompoundedPredicateImpl(Predicate.BooleanOperator.AND, Arrays.asList(restrictions), this); + } } @Override @@ -169,9 +180,11 @@ public Predicate or(Expression x, Expression y) { @Override public Predicate or(Predicate... restrictions) { - if (restrictions.length == 1) + if (restrictions.length == 1) { return new SimplePredicateImpl(Predicate.BooleanOperator.OR, restrictions[0], this); - else return new CompoundedPredicateImpl(Predicate.BooleanOperator.OR, Arrays.asList(restrictions), this); + } else { + return new CompoundedPredicateImpl(Predicate.BooleanOperator.OR, Arrays.asList(restrictions), this); + } } @Override @@ -190,7 +203,7 @@ public Predicate equal(Expression x, Object y) { public Predicate equal(Expression x, String y, String languageTag) { return new SimplePredicateImpl( new ExpressionEqualImpl((AbstractExpression) x, new ExpressionLiteralImpl<>(y, languageTag, this), - this), this); + this), this); } @Override @@ -218,7 +231,7 @@ public Predicate like(Expression x, Expression pattern) { public Predicate like(Expression x, String pattern) { return new SimplePredicateImpl( new ExpressionLikeImpl((AbstractExpression) x, new ExpressionLiteralImpl<>(pattern, this), - this), this); + this), this); } @Override @@ -232,7 +245,7 @@ public Predicate notLike(Expression x, Expression pattern) { public Predicate notLike(Expression x, String pattern) { return new SimplePredicateImpl( new ExpressionNotLikeImpl((AbstractExpression) x, new ExpressionLiteralImpl<>(pattern, this), - this), this); + this), this); } @Override @@ -289,7 +302,7 @@ public > Predicate greaterThanOrEqual(Expression public > Predicate greaterThanOrEqual(Expression x, Y y) { return new SimplePredicateImpl( new ExpressionGreaterThanOrEqualImpl((AbstractExpression) x, new ExpressionLiteralImpl<>(y, this), - this), this); + this), this); } @Override @@ -316,7 +329,7 @@ public > Predicate lessThanOrEqual(Expression> Predicate lessThanOrEqual(Expression x, Y y) { return new SimplePredicateImpl( new ExpressionLessThanOrEqualImpl((AbstractExpression) x, new ExpressionLiteralImpl<>(y, this), - this), this); + this), this); } @@ -335,7 +348,7 @@ public Predicate wrapExpressionToPredicateWithRepair(Expression express } else if (expression instanceof AbstractPathExpression) { return new SimplePredicateImpl( new ExpressionEqualImpl((AbstractExpression) expression, (AbstractExpression) this.literal(true), - this), this); + this), this); } else { return new SimplePredicateImpl(expression, this); } diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/ParameterExpressionImpl.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/ParameterExpressionImpl.java index 618ae51c2..702bdd7f9 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/ParameterExpressionImpl.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/criteria/expressions/ParameterExpressionImpl.java @@ -30,7 +30,9 @@ public ParameterExpressionImpl(Class type, String name, CriteriaBuilder cb) { } public void setNameIfUnnamed(String name) { - if (this.name == null) this.name = name; + if (this.name == null) { + this.name = name; + } } @Override @@ -56,7 +58,9 @@ public void setExpressionToQuery(StringBuilder query, CriteriaParameterFiller pa @Override public boolean equals(Object o) { - if (this == o) return true; + if (this == o) { + return true; + } if (o == null || !Parameter.class.isAssignableFrom(o.getClass())) { return false; } diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/soql/SoqlFunctionTranslator.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/soql/SoqlFunctionTranslator.java index 037f8f76d..4d37b99cc 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/soql/SoqlFunctionTranslator.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/query/soql/SoqlFunctionTranslator.java @@ -16,7 +16,6 @@ import cz.cvut.kbss.jopa.exception.QueryParserException; -import java.util.HashMap; import java.util.Locale; import java.util.Map; @@ -25,19 +24,15 @@ */ class SoqlFunctionTranslator { - private static final Map FUNCTION_MAP = initFunctions(); - - private static Map initFunctions() { - final Map map = new HashMap<>(); - map.put(SoqlConstants.Functions.UPPER, "UCASE"); - map.put(SoqlConstants.Functions.LOWER, "LCASE"); - map.put(SoqlConstants.Functions.LENGTH, "STRLEN"); - map.put(SoqlConstants.Functions.ABS, "ABS"); - map.put(SoqlConstants.Functions.CEIL, "CEIL"); - map.put(SoqlConstants.Functions.FLOOR, "FLOOR"); - map.put(SoqlConstants.Functions.LANG, "lang"); - return map; - } + private static final Map FUNCTION_MAP = Map.of( + SoqlConstants.Functions.UPPER, "UCASE", + SoqlConstants.Functions.LOWER, "LCASE", + SoqlConstants.Functions.LENGTH, "STRLEN", + SoqlConstants.Functions.ABS, "ABS", + SoqlConstants.Functions.CEIL, "CEIL", + SoqlConstants.Functions.FLOOR, "FLOOR", + SoqlConstants.Functions.LANG, "lang" + ); private SoqlFunctionTranslator() { throw new AssertionError(); diff --git a/jopa-integration-tests-jena/pom.xml b/jopa-integration-tests-jena/pom.xml index a9d84a1a0..e49dec006 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 - 1.1.0 + 1.1.1 ../pom.xml 4.0.0 diff --git a/jopa-integration-tests-owlapi/pom.xml b/jopa-integration-tests-owlapi/pom.xml index 6793d2d47..b9ec78406 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 - 1.1.0 + 1.1.1 ../pom.xml 4.0.0 diff --git a/jopa-integration-tests-rdf4j/pom.xml b/jopa-integration-tests-rdf4j/pom.xml index 7bb265dea..ee75e5bfd 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 - 1.1.0 + 1.1.1 ../pom.xml jopa-integration-tests-rdf4j diff --git a/jopa-integration-tests/pom.xml b/jopa-integration-tests/pom.xml index 3e0df19bd..d21725652 100644 --- a/jopa-integration-tests/pom.xml +++ b/jopa-integration-tests/pom.xml @@ -6,7 +6,7 @@ cz.cvut.kbss.jopa jopa-all - 1.1.0 + 1.1.1 ../pom.xml 4.0.0 diff --git a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/OWLChildClassA.java b/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/OWLChildClassA.java index 202ee36aa..e6bea0b2b 100644 --- a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/OWLChildClassA.java +++ b/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/OWLChildClassA.java @@ -21,12 +21,18 @@ @OWLClass(iri = Vocabulary.C_OWL_CLASS_CHILD_A) public class OWLChildClassA implements OWLParentA, OWLParentB { + + @Id + private URI id; + + @Types(fetchType = FetchType.EAGER) + private Set types; + @OWLAnnotationProperty(iri = Vocabulary.DC_SOURCE) private Set pluralAnnotationProperty; + @OWLDataProperty(iri = Vocabulary.P_E_STRING_ATTRIBUTE) private String stringAttribute; - @Id - private URI id; public URI getId() { return id; @@ -35,8 +41,6 @@ public URI getId() { public void setId(URI id) { this.id = id; } - @Types(fetchType = FetchType.EAGER) - private Set types; public Set getTypes() { return types; @@ -65,5 +69,4 @@ public Set getPluralAnnotationProperty() { public void setPluralAnnotationProperty(Set pluralAnnotationProperty) { this.pluralAnnotationProperty = pluralAnnotationProperty; } - } diff --git a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/OWLClassA.java b/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/OWLClassA.java index 356fd3bf5..eb6e1e5b3 100644 --- a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/OWLClassA.java +++ b/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/OWLClassA.java @@ -14,7 +14,18 @@ */ package cz.cvut.kbss.jopa.test; -import cz.cvut.kbss.jopa.model.annotations.*; +import cz.cvut.kbss.jopa.model.annotations.ConstructorResult; +import cz.cvut.kbss.jopa.model.annotations.EntityResult; +import cz.cvut.kbss.jopa.model.annotations.FieldResult; +import cz.cvut.kbss.jopa.model.annotations.Id; +import cz.cvut.kbss.jopa.model.annotations.NamedNativeQueries; +import cz.cvut.kbss.jopa.model.annotations.NamedNativeQuery; +import cz.cvut.kbss.jopa.model.annotations.OWLClass; +import cz.cvut.kbss.jopa.model.annotations.OWLDataProperty; +import cz.cvut.kbss.jopa.model.annotations.SparqlResultSetMapping; +import cz.cvut.kbss.jopa.model.annotations.SparqlResultSetMappings; +import cz.cvut.kbss.jopa.model.annotations.Types; +import cz.cvut.kbss.jopa.model.annotations.VariableResult; import java.net.URI; import java.util.Objects; @@ -35,7 +46,7 @@ }) })}) @NamedNativeQueries({@NamedNativeQuery(name = "OWLClassA.findAll", - query = "SELECT ?x WHERE {?x a <" + Vocabulary.C_OWL_CLASS_A + "> . } ORDER BY ?x"), + query = "SELECT ?x WHERE {?x a <" + Vocabulary.C_OWL_CLASS_A + "> . } ORDER BY ?x"), @NamedNativeQuery(name = "OWLClassA.findByString", query = "SELECT ?x WHERE { ?x <" + Vocabulary.P_A_STRING_ATTRIBUTE + "> ?str . }") }) @@ -94,8 +105,12 @@ public Set getTypes() { @Override public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof OWLClassA)) return false; + if (this == o) { + return true; + } + if (!(o instanceof OWLClassA)) { + return false; + } OWLClassA owlClassA = (OWLClassA) o; return Objects.equals(getTypes(), owlClassA.getTypes()) && Objects.equals(getUri(), owlClassA.getUri()) && Objects.equals(getStringAttribute(), owlClassA.getStringAttribute()); } diff --git a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/OWLClassWithAnnotatedMethodsInInterfaceParent.java b/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/OWLClassWithAnnotatedMethodsInInterfaceParent.java index 2a8009b97..41b6c013a 100644 --- a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/OWLClassWithAnnotatedMethodsInInterfaceParent.java +++ b/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/OWLClassWithAnnotatedMethodsInInterfaceParent.java @@ -25,21 +25,21 @@ @OWLClass(iri = Vocabulary.C_OWL_CLASS_PART_CONSTR_IN_PARENT) public class OWLClassWithAnnotatedMethodsInInterfaceParent implements OWLInterfaceE { + @Id + private URI uri; + protected OWLClassWithUnProperties data; protected Set dataList; protected ZoneOffset withConverter; protected Color ordinalEnumAttribute; protected List simpleList; - public OWLClassWithAnnotatedMethodsInInterfaceParent(URI uri) { - this.uri = uri; - } - public OWLClassWithAnnotatedMethodsInInterfaceParent() { } - @Id - private URI uri; + public OWLClassWithAnnotatedMethodsInInterfaceParent(URI uri) { + this.uri = uri; + } public URI getUri() { return uri; diff --git a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/OWLClassWithUnProperties.java b/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/OWLClassWithUnProperties.java index 3dd219d76..fed4e0379 100644 --- a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/OWLClassWithUnProperties.java +++ b/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/OWLClassWithUnProperties.java @@ -33,6 +33,13 @@ public class OWLClassWithUnProperties implements OWLInterfaceAnMethods { private Set titles; + public OWLClassWithUnProperties() { + } + + public OWLClassWithUnProperties(URI id) { + this.id = id; + } + public URI getId() { return id; } @@ -45,13 +52,6 @@ public String getName() { return name; } - public OWLClassWithUnProperties() { - } - - public OWLClassWithUnProperties(URI id) { - this.id = id; - } - @Override public void setName(String name) { this.name = name; @@ -73,6 +73,4 @@ public Set getTitles() { public void setTitles(Set titles) { this.titles = titles; } - - } 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 cd19dd00d..844c72bb6 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 @@ -14,7 +14,13 @@ */ package cz.cvut.kbss.jopa.test; -import cz.cvut.kbss.jopa.model.annotations.*; +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.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.vocabulary.RDFS; import java.net.URI; @@ -32,7 +38,8 @@ public class OWLClassZChild { @OWLDataProperty(iri = RDFS.LABEL) private String name; - @OWLObjectProperty(iri = Vocabulary.ATTRIBUTE_IRI_BASE + "hasChild", cascade = CascadeType.ALL, fetch = FetchType.EAGER) + @OWLObjectProperty(iri = Vocabulary.ATTRIBUTE_IRI_BASE + "hasChild", cascade = CascadeType.ALL, + fetch = FetchType.EAGER) private Set children = new HashSet<>(); public URI getId() { @@ -61,8 +68,12 @@ public void setChildren(Set children) { @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } OWLClassZChild that = (OWLClassZChild) o; return Objects.equals(id, that.id); } diff --git a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/environment/TestEnvironment.java b/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/environment/TestEnvironment.java index e29d1cece..700f9c891 100644 --- a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/environment/TestEnvironment.java +++ b/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/environment/TestEnvironment.java @@ -69,7 +69,7 @@ public static List getPersistenceConnector(String baseName, for (StorageConfig si : storages) { si.setName(baseName); si.setDirectory(TEST_RESULTS_DIR); - final Map config = si.createStorageConfiguration(i++); + final Map config = new HashMap<>(si.createStorageConfiguration(i++)); config.putAll(params); final EntityManager em = Persistence.createEntityManagerFactory("context-name_" + i, config) .createEntityManager(); diff --git a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/query/QueryTestEnvironment.java b/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/query/QueryTestEnvironment.java index f841f1a57..9b0b80786 100644 --- a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/query/QueryTestEnvironment.java +++ b/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/query/QueryTestEnvironment.java @@ -282,13 +282,13 @@ private static List generateOwlClassYInstances() { y.setSingularString(MultilingualString.create("Test" + i, TestEnvironment.PERSISTENCE_LANGUAGE)); switch (Generators.randomPositiveInt(0, 3)) { case 0: - y.getSingularString().set("Testwert nummer " + i, "de"); + y.getSingularString().set("de", "Testwert nummer " + i); break; case 1: - y.getSingularString().set("Testovací hodnota číslo " + i, "cs"); + y.getSingularString().set("cs", "Testovací hodnota číslo " + i); break; case 2: - y.getSingularString().set("nombre de valeurs de test " + i, "fr"); + y.getSingularString().set("fr", "nombre de valeurs de test " + i); break; } lst.add(y); diff --git a/jopa-maven-plugin/pom.xml b/jopa-maven-plugin/pom.xml index b7e413500..77edd83bb 100644 --- a/jopa-maven-plugin/pom.xml +++ b/jopa-maven-plugin/pom.xml @@ -5,7 +5,7 @@ jopa-all cz.cvut.kbss.jopa - 1.1.0 + 1.1.1 ../pom.xml 4.0.0 diff --git a/jopa-owl2java/pom.xml b/jopa-owl2java/pom.xml index 09041149e..8f5b11017 100644 --- a/jopa-owl2java/pom.xml +++ b/jopa-owl2java/pom.xml @@ -6,7 +6,7 @@ cz.cvut.kbss.jopa jopa-all - 1.1.0 + 1.1.1 ../pom.xml diff --git a/jopa-owlapi-utils/pom.xml b/jopa-owlapi-utils/pom.xml index 11afdbf78..bdb9c2e44 100644 --- a/jopa-owlapi-utils/pom.xml +++ b/jopa-owlapi-utils/pom.xml @@ -6,7 +6,7 @@ cz.cvut.kbss.jopa jopa-all - 1.1.0 + 1.1.1 ../pom.xml diff --git a/modelgen/pom.xml b/modelgen/pom.xml index 629fd0fcc..a1ccd0e21 100644 --- a/modelgen/pom.xml +++ b/modelgen/pom.xml @@ -6,7 +6,7 @@ jopa-all cz.cvut.kbss.jopa - 1.1.0 + 1.1.1 ../pom.xml diff --git a/modelgen/src/main/java/cz/cvut/kbss/jopa/modelgen/ModelGenProcessor.java b/modelgen/src/main/java/cz/cvut/kbss/jopa/modelgen/ModelGenProcessor.java index 6b1714bf6..ad5a35282 100644 --- a/modelgen/src/main/java/cz/cvut/kbss/jopa/modelgen/ModelGenProcessor.java +++ b/modelgen/src/main/java/cz/cvut/kbss/jopa/modelgen/ModelGenProcessor.java @@ -83,7 +83,7 @@ public boolean process(Set annotations, RoundEnvironment } List properties = elParent.getEnclosedElements(); for (Element elProperty : properties) { - if (propertyIsWanted(elProperty)) { + if (isPropertyPersistent(elProperty)) { Field field = new Field(elProperty, elParent); if (debugOption) { messager.printMessage(Diagnostic.Kind.NOTE, @@ -109,7 +109,7 @@ public boolean process(Set annotations, RoundEnvironment return true; } - private boolean propertyIsWanted(Element param) { + private static boolean isPropertyPersistent(Element param) { boolean containsWanted = false; List paramAnnotations = param.getAnnotationMirrors(); if (!paramAnnotations.isEmpty()) { 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 a707d078f..ce0e7ce17 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 @@ -125,9 +125,9 @@ public String generateClassPreamble(MetamodelClass cls) { .append(cls.getPckg()) .append(";\n\n"); } - cls.getImports().forEach((imp) -> sbOut.append("import ") - .append(imp) - .append(";\n")); + cls.getImports().forEach(imp -> sbOut.append("import ") + .append(imp) + .append(";\n")); if (!cls.getExtend().isEmpty()) { sbOut.append("import ") .append(cls.getExtend()) @@ -148,7 +148,7 @@ public String generateClassPreamble(MetamodelClass cls) { return sbOut.toString(); } - private String generateAttributes(MetamodelClass cls) { + private static String generateAttributes(MetamodelClass cls) { StringBuilder attributes = new StringBuilder(); for (Field field : cls.getFields()) { final String declaringClass = field.getParentName().substring(field.getParentName().lastIndexOf('.') + 1); @@ -231,7 +231,7 @@ private String generateAttributes(MetamodelClass cls) { return attributes.toString(); } - private String generateClassSuffix() { + private static String generateClassSuffix() { return "}"; } diff --git a/modelgen/src/main/java/cz/cvut/kbss/jopa/modelgen/classmodel/MetamodelClass.java b/modelgen/src/main/java/cz/cvut/kbss/jopa/modelgen/classmodel/MetamodelClass.java index 8a3b9a681..f5bb17299 100644 --- a/modelgen/src/main/java/cz/cvut/kbss/jopa/modelgen/classmodel/MetamodelClass.java +++ b/modelgen/src/main/java/cz/cvut/kbss/jopa/modelgen/classmodel/MetamodelClass.java @@ -42,7 +42,9 @@ public MetamodelClass(Element elClass) { } this.name = elClass.getSimpleName().toString(); this.extend = ((TypeElement) elClass).getSuperclass().toString(); - if (extend.equals(Object.class.getName())) extend = ""; + if (extend.equals(Object.class.getName())) { + extend = ""; + } else if (extend.contains("<")) { this.extend = extend.substring(0, extend.indexOf("<")); } diff --git a/modelgen/src/main/java/cz/cvut/kbss/jopa/modelgen/classmodel/Type.java b/modelgen/src/main/java/cz/cvut/kbss/jopa/modelgen/classmodel/Type.java index 34eee151a..2595d389e 100644 --- a/modelgen/src/main/java/cz/cvut/kbss/jopa/modelgen/classmodel/Type.java +++ b/modelgen/src/main/java/cz/cvut/kbss/jopa/modelgen/classmodel/Type.java @@ -51,7 +51,7 @@ public Type(TypeMirror tMirror) { } } - private TypeMirror getUpperBound(TypeMirror type) { + private static TypeMirror getUpperBound(TypeMirror type) { if (type instanceof TypeVariable) { return ((TypeVariable) type).getUpperBound(); } @@ -82,7 +82,7 @@ public void setTypes(List types) { this.types = types; } - private boolean isSimple(String name) { + private static boolean isSimple(String name) { return !name.contains(Set.class.getName()) && !name.contains(List.class.getName()) && !name.contains(Stack.class.getName()) diff --git a/ontodriver-api/pom.xml b/ontodriver-api/pom.xml index 7200883f8..b1c4f7833 100644 --- a/ontodriver-api/pom.xml +++ b/ontodriver-api/pom.xml @@ -6,7 +6,7 @@ jopa-all cz.cvut.kbss.jopa - 1.1.0 + 1.1.1 ../pom.xml diff --git a/ontodriver-api/src/main/java/cz/cvut/kbss/ontodriver/config/DriverConfiguration.java b/ontodriver-api/src/main/java/cz/cvut/kbss/ontodriver/config/DriverConfiguration.java index b8578cb93..2ce9011a3 100644 --- a/ontodriver-api/src/main/java/cz/cvut/kbss/ontodriver/config/DriverConfiguration.java +++ b/ontodriver-api/src/main/java/cz/cvut/kbss/ontodriver/config/DriverConfiguration.java @@ -82,6 +82,23 @@ public String getProperty(ConfigurationParameter property, String defaultValue) return configuration.getOrDefault(property, defaultValue); } + /** + * Gets integer value of the specified property or the default value, if the property is not set. + * + * @param property Parameter + * @param defaultValue Value to return if the property is not set + * @return Value of the property or {@code defaultValue}, if it is not set + * @throws IllegalArgumentException If the configured property value is not an integer + */ + public int getProperty(ConfigurationParameter property, int defaultValue) { + final String propertyValue = getProperty(property, Integer.toString(defaultValue)); + try { + return Integer.parseInt(propertyValue); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Value '" + propertyValue + "' of property '" + propertyValue + "' is not a valid integer.", e); + } + } + /** * Returns value of the specified property as boolean. *

diff --git a/ontodriver-api/src/main/java/cz/cvut/kbss/ontodriver/model/PropertyAssertion.java b/ontodriver-api/src/main/java/cz/cvut/kbss/ontodriver/model/PropertyAssertion.java index 391e6b2f0..40ab9d6a5 100644 --- a/ontodriver-api/src/main/java/cz/cvut/kbss/ontodriver/model/PropertyAssertion.java +++ b/ontodriver-api/src/main/java/cz/cvut/kbss/ontodriver/model/PropertyAssertion.java @@ -19,7 +19,7 @@ class PropertyAssertion extends Assertion { - private static final URI UNSPECIFIED_PROPERTY = URI.create("http://" + Math.abs(new Random().nextInt())); + private static final URI UNSPECIFIED_PROPERTY = URI.create("http://" + Math.abs(new Random().nextInt(Integer.MAX_VALUE))); PropertyAssertion(boolean isInferred) { super(UNSPECIFIED_PROPERTY, isInferred); diff --git a/ontodriver-api/src/test/java/cz/cvut/kbss/ontodriver/config/DriverConfigurationTest.java b/ontodriver-api/src/test/java/cz/cvut/kbss/ontodriver/config/DriverConfigurationTest.java index 7fa4d190c..d5d08bdec 100644 --- a/ontodriver-api/src/test/java/cz/cvut/kbss/ontodriver/config/DriverConfigurationTest.java +++ b/ontodriver-api/src/test/java/cz/cvut/kbss/ontodriver/config/DriverConfigurationTest.java @@ -60,4 +60,25 @@ void testGetPropertyAsBoolean() { void getAsBooleanReturnsFalseForUnknownProperty() { assertFalse(configuration.is(DriverConfigParam.AUTO_COMMIT)); } + + @Test + void getPropertyAsIntegerReturnsParsedPropertyValue() { + configuration.setProperty(TestConfigParameter.INTEGER_PARAM, "117"); + assertEquals(117, configuration.getProperty(TestConfigParameter.INTEGER_PARAM, 1)); + } + + @Test + void getPropertyAsIntegerReturnsDefaultValueWhenPropertyIsNotConfigured() { + assertEquals(1, configuration.getProperty(TestConfigParameter.INTEGER_PARAM, 1)); + } + + @Test + void getPropertyAsIntegerThrowsIllegalArgumentExceptionWhenConfiguredPropertyValueIsNotInteger() { + configuration.setProperty(TestConfigParameter.INTEGER_PARAM, "dkde"); + assertThrows(IllegalArgumentException.class, () -> configuration.getProperty(TestConfigParameter.INTEGER_PARAM, 1)); + } + + private enum TestConfigParameter implements ConfigurationParameter { + INTEGER_PARAM + } } diff --git a/ontodriver-jena/pom.xml b/ontodriver-jena/pom.xml index f29be7caa..153891eb1 100644 --- a/ontodriver-jena/pom.xml +++ b/ontodriver-jena/pom.xml @@ -8,7 +8,7 @@ cz.cvut.kbss.jopa jopa-all - 1.1.0 + 1.1.1 ../pom.xml diff --git a/ontodriver-owlapi/pom.xml b/ontodriver-owlapi/pom.xml index c31c0cea9..84ce4aa20 100644 --- a/ontodriver-owlapi/pom.xml +++ b/ontodriver-owlapi/pom.xml @@ -11,7 +11,7 @@ cz.cvut.kbss.jopa jopa-all - 1.1.0 + 1.1.1 ../pom.xml diff --git a/ontodriver-rdf4j/pom.xml b/ontodriver-rdf4j/pom.xml index e18804626..352968300 100644 --- a/ontodriver-rdf4j/pom.xml +++ b/ontodriver-rdf4j/pom.xml @@ -10,11 +10,11 @@ jopa-all cz.cvut.kbss.jopa - 1.1.0 + 1.1.1 - 4.3.4 + 4.3.5 2.0.9 diff --git a/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/config/Constants.java b/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/config/Constants.java index de1094c7e..bc4ce8101 100644 --- a/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/config/Constants.java +++ b/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/config/Constants.java @@ -38,6 +38,20 @@ public class Constants { */ public static final int DEFAULT_RECONNECT_ATTEMPTS_COUNT = 5; + /** + * Default maximum connection pool size. + * + * @see Rdf4jOntoDriverProperties#MAX_CONNECTION_POOL_SIZE + */ + public static final int DEFAULT_MAX_CONNECTIONS = 5; + + /** + * Default connection request timeout (in milliseconds). + * + * @see Rdf4jOntoDriverProperties#CONNECTION_REQUEST_TIMEOUT + */ + public static final int DEFAULT_CONNECTION_REQUEST_TIMEOUT = 1000; + private Constants() { throw new AssertionError(); } diff --git a/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/config/Rdf4jConfigParam.java b/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/config/Rdf4jConfigParam.java index 4c1d5bc45..ce3f5332f 100644 --- a/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/config/Rdf4jConfigParam.java +++ b/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/config/Rdf4jConfigParam.java @@ -32,7 +32,9 @@ public enum Rdf4jConfigParam implements ConfigurationParameter { PASSWORD(OntoDriverProperties.DATA_SOURCE_PASSWORD), REPOSITORY_CONFIG(Rdf4jOntoDriverProperties.REPOSITORY_CONFIG), RECONNECT_ATTEMPTS(Rdf4jOntoDriverProperties.RECONNECT_ATTEMPTS), - INFERENCE_IN_DEFAULT_CONTEXT(Rdf4jOntoDriverProperties.INFERENCE_IN_DEFAULT_CONTEXT); + INFERENCE_IN_DEFAULT_CONTEXT(Rdf4jOntoDriverProperties.INFERENCE_IN_DEFAULT_CONTEXT), + CONNECTION_REQUEST_TIMEOUT(Rdf4jOntoDriverProperties.CONNECTION_REQUEST_TIMEOUT), + MAX_CONNECTION_POOL_SIZE(Rdf4jOntoDriverProperties.MAX_CONNECTION_POOL_SIZE); private final String name; diff --git a/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/config/Rdf4jOntoDriverProperties.java b/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/config/Rdf4jOntoDriverProperties.java index d8c2a28d3..6d5248b0e 100644 --- a/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/config/Rdf4jOntoDriverProperties.java +++ b/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/config/Rdf4jOntoDriverProperties.java @@ -64,9 +64,9 @@ public abstract class Rdf4jOntoDriverProperties { * be loaded using the value as file path. The loaded configuration supersedes relevant properties passed to the * driver, e.g., {@link #USE_VOLATILE_STORAGE} or {@link #USE_INFERENCE}. *

- * Note that the config applies only to embedded repositories created by the driver, repositories on a RDF4J - * server to which the driver just connects must preexist and the configuration does not apply to them. The physical - * URI specified in configuration must correspond to the URI of the repository in the config file, i.e., for memory + * Note that the config applies only to embedded repositories created by the driver, repositories on a RDF4J server + * to which the driver just connects must preexist and the configuration does not apply to them. The physical URI + * specified in configuration must correspond to the URI of the repository in the config file, i.e., for memory * store, the repository ID must be the same. For a native store, the physical URI must be in the form {@code * /local-path/repositories/repository-id}, where {@code local-path} will be used for initialization of a local * {@link org.eclipse.rdf4j.repository.manager.RepositoryManager} and {@code repository-id} must again correspond to @@ -98,8 +98,31 @@ public abstract class Rdf4jOntoDriverProperties { *

* Defaults to {@code false}, i.e., inferred statements are expected in the same context as their causes. */ - public static final String INFERENCE_IN_DEFAULT_CONTEXT = - "cz.cvut.kbss.ontodriver.rdf4j.inference-in-default-context"; + public static final String INFERENCE_IN_DEFAULT_CONTEXT = "cz.cvut.kbss.ontodriver.rdf4j.inference-in-default-context"; + + /** + * Timeout for the underlying HTTP client to request a connection to the remote repository from the connection + * pool. + *

+ * RDF4J internally uses the Apache HTTP Client with a connection pool that is able to grow up to a configured + * limit. When the pool is exhausted and the driver requests another connection, it may happen that no connection is + * available will become available, possibly leading to a deadlock. This parameter sets a timeout for the connection + * acquisition requests from the pool, so that the application is able to fail gracefully in case of too much load + * and does not get stuck. + *

+ * The value should be an integer corresponding to the number of milliseconds. + *

+ * Applies only to instances connected to a RDF4J server. + */ + public static final String CONNECTION_REQUEST_TIMEOUT = "cz.cvut.kbss.ontodriver.rdf4j.connection-request-timeout"; + + /** + * Maximum size of the underlying HTTP client connection pool. + *

+ * RDF4J internally uses the Apache HTTP Client with a connection pool that is able to grow up to a configured + * limit. This parameter allows to set this limit. + */ + public static final String MAX_CONNECTION_POOL_SIZE = "cz.cvut.kbss.ontodriver.rdf4j.max-connections"; private Rdf4jOntoDriverProperties() { throw new AssertionError(); diff --git a/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/connector/StorageConnector.java b/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/connector/StorageConnector.java index 0d2668df5..5b59f2abd 100644 --- a/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/connector/StorageConnector.java +++ b/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/connector/StorageConnector.java @@ -14,6 +14,7 @@ */ package cz.cvut.kbss.ontodriver.rdf4j.connector; +import cz.cvut.kbss.ontodriver.Wrapper; import cz.cvut.kbss.ontodriver.exception.OntoDriverException; import cz.cvut.kbss.ontodriver.rdf4j.connector.init.RepositoryConnectorInitializer; import cz.cvut.kbss.ontodriver.rdf4j.exception.Rdf4jDriverException; @@ -259,6 +260,9 @@ public T unwrap(Class cls) throws OntoDriverException { if (cls.isAssignableFrom(repository.getClass())) { return cls.cast(repository); } + if (repository instanceof Wrapper) { + return ((Wrapper) repository).unwrap(cls); + } throw new Rdf4jDriverException("No instance of class " + cls + " found."); } diff --git a/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/connector/init/HttpClientFactory.java b/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/connector/init/HttpClientFactory.java new file mode 100644 index 000000000..454d1634e --- /dev/null +++ b/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/connector/init/HttpClientFactory.java @@ -0,0 +1,143 @@ +package cz.cvut.kbss.ontodriver.rdf4j.connector.init; + +import cz.cvut.kbss.ontodriver.config.DriverConfiguration; +import cz.cvut.kbss.ontodriver.rdf4j.config.Constants; +import cz.cvut.kbss.ontodriver.rdf4j.config.Rdf4jConfigParam; +import org.apache.http.HttpConnection; +import org.apache.http.HttpResponse; +import org.apache.http.client.HttpClient; +import org.apache.http.client.HttpRequestRetryHandler; +import org.apache.http.client.ServiceUnavailableRetryStrategy; +import org.apache.http.client.config.CookieSpecs; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.protocol.HttpClientContext; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.protocol.HttpContext; +import org.eclipse.rdf4j.http.client.SharedHttpClientSessionManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.net.HttpURLConnection; + +class HttpClientFactory { + + /** + * Creates a customized {@link HttpClient} instance. + *

+ * The client's configuration is basically the same as the default one used by RDF4J, but it sets a timeout on + * connection requests from the connection pool, so that an application with exhausted connection pool fails + * gracefully instead of potentially going into a deadlock. + * + * @param configuration Driver configuration + * @return HttpClient instance with connection pool request timeout + */ + static HttpClient createHttpClient(DriverConfiguration configuration) { + final RequestConfig customRequestConfig = RequestConfig.custom() + .setCookieSpec(CookieSpecs.STANDARD) + .setConnectionRequestTimeout(getConnectionRequestTimeout(configuration)) + .build(); + final int maxConnections = getMaxConnections(configuration); + return HttpClientBuilder.create() + .evictExpiredConnections() + .setRetryHandler(new RetryHandlerStale()) + .setServiceUnavailableRetryStrategy(new ServiceUnavailableRetryHandler()) + .useSystemProperties() + // Set max connections per route to the same value as max connections, as we are always + // connecting to the same remote (RDF4J server repository) + .setMaxConnPerRoute(maxConnections) + .setMaxConnTotal(maxConnections) + .setDefaultRequestConfig(customRequestConfig).build(); + } + + private static int getConnectionRequestTimeout(DriverConfiguration config) { + return config.getProperty(Rdf4jConfigParam.CONNECTION_REQUEST_TIMEOUT, Constants.DEFAULT_CONNECTION_REQUEST_TIMEOUT); + } + + private static int getMaxConnections(DriverConfiguration config) { + final int defaultMaxConnections = Math.max(Constants.DEFAULT_MAX_CONNECTIONS, Runtime.getRuntime() + .availableProcessors() * 2); + return config.getProperty(Rdf4jConfigParam.MAX_CONNECTION_POOL_SIZE, defaultMaxConnections); + } + + /** + * Copied from {@link SharedHttpClientSessionManager} + */ + private static class RetryHandlerStale implements HttpRequestRetryHandler { + private final Logger logger = LoggerFactory.getLogger(RetryHandlerStale.class); + + @Override + public boolean retryRequest(IOException ioe, int count, HttpContext context) { + // only try this once + if (count > 1) { + return false; + } + HttpClientContext clientContext = HttpClientContext.adapt(context); + HttpConnection conn = clientContext.getConnection(); + if (conn != null) { + synchronized (this) { + if (conn.isStale()) { + try { + logger.warn("Closing stale connection"); + conn.close(); + return true; + } catch (IOException e) { + logger.error("Error closing stale connection", e); + } + } + } + } + return false; + } + } + + /** + * Copied from {@link SharedHttpClientSessionManager} + */ + private static class ServiceUnavailableRetryHandler implements ServiceUnavailableRetryStrategy { + private final Logger logger = LoggerFactory.getLogger(ServiceUnavailableRetryHandler.class); + + @Override + public boolean retryRequest(HttpResponse response, int executionCount, HttpContext context) { + // only retry on `408` + if (response.getStatusLine().getStatusCode() != HttpURLConnection.HTTP_CLIENT_TIMEOUT) { + return false; + } + + // when `keepAlive` is disabled every connection is fresh (with the default `useSystemProperties` http + // client configuration we use), a 408 in that case is an unexpected issue we don't handle here + String keepAlive = System.getProperty("http.keepAlive", "true"); + if (!"true".equalsIgnoreCase(keepAlive)) { + return false; + } + + // worst case, the connection pool is filled to the max and all of them idled out on the server already + // we then need to clean up the pool and finally retry with a fresh connection. Hence, we need at most + // pooledConnections+1 retries. + // the pool size setting used here is taken from `HttpClientBuilder` when `useSystemProperties()` is used + int pooledConnections = Integer.parseInt(System.getProperty("http.maxConnections", "5")); + if (executionCount > (pooledConnections + 1)) { + return false; + } + + HttpClientContext clientContext = HttpClientContext.adapt(context); + HttpConnection conn = clientContext.getConnection(); + + synchronized (this) { + try { + logger.info("Cleaning up closed connection"); + conn.close(); + return true; + } catch (IOException e) { + logger.error("Error cleaning up closed connection", e); + } + } + return false; + } + + @Override + public long getRetryInterval() { + return 1000; + } + } +} diff --git a/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/connector/init/RemoteRepositoryWrapper.java b/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/connector/init/RemoteRepositoryWrapper.java new file mode 100644 index 000000000..5d3ca7aef --- /dev/null +++ b/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/connector/init/RemoteRepositoryWrapper.java @@ -0,0 +1,87 @@ +package cz.cvut.kbss.ontodriver.rdf4j.connector.init; + +import cz.cvut.kbss.ontodriver.Wrapper; +import cz.cvut.kbss.ontodriver.config.DriverConfiguration; +import cz.cvut.kbss.ontodriver.exception.OntoDriverException; +import cz.cvut.kbss.ontodriver.rdf4j.exception.Rdf4jDriverException; +import org.apache.http.client.HttpClient; +import org.apache.http.client.utils.HttpClientUtils; +import org.eclipse.rdf4j.http.client.HttpClientSessionManager; +import org.eclipse.rdf4j.http.client.SharedHttpClientSessionManager; +import org.eclipse.rdf4j.model.ValueFactory; +import org.eclipse.rdf4j.repository.Repository; +import org.eclipse.rdf4j.repository.RepositoryConnection; +import org.eclipse.rdf4j.repository.RepositoryException; +import org.eclipse.rdf4j.repository.http.HTTPRepository; + +import java.io.File; + +/** + * Wrapper for RDF4J {@link HTTPRepository} allowing to set custom {@link HttpClient} for it to use. + */ +public class RemoteRepositoryWrapper implements Repository, Wrapper { + + private final HTTPRepository delegate; + private HttpClient httpClient; + + public RemoteRepositoryWrapper(HTTPRepository delegate, DriverConfiguration configuration) { + this.delegate = delegate; + this.httpClient = HttpClientFactory.createHttpClient(configuration); + final HttpClientSessionManager sessionManager = delegate.getHttpClientSessionManager(); + if (sessionManager instanceof SharedHttpClientSessionManager) { + ((SharedHttpClientSessionManager) sessionManager).setHttpClient(httpClient); + } + } + + @Override + public void setDataDir(File file) { + delegate.setDataDir(file); + } + + @Override + public File getDataDir() { + return delegate.getDataDir(); + } + + @Override + public void init() throws RepositoryException { + delegate.init(); + } + + @Override + public boolean isInitialized() { + return delegate.isInitialized(); + } + + @Override + public void shutDown() throws RepositoryException { + delegate.shutDown(); + if (httpClient != null) { + HttpClientUtils.closeQuietly(httpClient); + this.httpClient = null; + } + } + + @Override + public boolean isWritable() throws RepositoryException { + return delegate.isWritable(); + } + + @Override + public RepositoryConnection getConnection() throws RepositoryException { + return delegate.getConnection(); + } + + @Override + public ValueFactory getValueFactory() { + return delegate.getValueFactory(); + } + + @Override + public T unwrap(Class cls) throws OntoDriverException { + if (cls.isAssignableFrom(delegate.getClass())) { + return cls.cast(delegate); + } + throw new Rdf4jDriverException("No instance of class " + cls + " found."); + } +} diff --git a/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/connector/init/RepositoryConnectorInitializer.java b/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/connector/init/RepositoryConnectorInitializer.java index 66717d81b..7ef9c4800 100644 --- a/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/connector/init/RepositoryConnectorInitializer.java +++ b/ontodriver-rdf4j/src/main/java/cz/cvut/kbss/ontodriver/rdf4j/connector/init/RepositoryConnectorInitializer.java @@ -1,16 +1,14 @@ /** * Copyright (C) 2023 Czech Technical University in Prague - * - * This program is free software: you can redistribute it and/or modify it under - * the terms of the GNU General Public License as published by the Free Software - * Foundation, either version 3 of the License, or (at your option) any - * later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. You should have received a copy of the GNU General Public License - * along with this program. If not, see . + *

+ * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later + * version. + *

+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. You should have received a copy of the GNU General Public License along with this program. If not, see + * . */ package cz.cvut.kbss.ontodriver.rdf4j.connector.init; @@ -30,6 +28,7 @@ import org.eclipse.rdf4j.repository.config.RepositoryConfig; import org.eclipse.rdf4j.repository.config.RepositoryConfigException; import org.eclipse.rdf4j.repository.config.RepositoryConfigSchema; +import org.eclipse.rdf4j.repository.http.HTTPRepository; import org.eclipse.rdf4j.repository.manager.RemoteRepositoryManager; import org.eclipse.rdf4j.repository.manager.RepositoryManager; import org.eclipse.rdf4j.repository.manager.RepositoryProvider; @@ -77,7 +76,7 @@ private int resolveMaxReconnectAttempts() throws Rdf4jDriverException { try { final int attempts = configuration.isSet(Rdf4jConfigParam.RECONNECT_ATTEMPTS) ? Integer.parseInt( configuration.getProperty(Rdf4jConfigParam.RECONNECT_ATTEMPTS)) : - Constants.DEFAULT_RECONNECT_ATTEMPTS_COUNT; + Constants.DEFAULT_RECONNECT_ATTEMPTS_COUNT; if (attempts < 0) { throw invalidReconnectAttemptsConfig(); } @@ -133,7 +132,7 @@ private Repository connectToRemoteRepository(String repoUri) { private Repository connectToRemote(String repoUri, int attempts) { try { - return manager.getRepository(RepositoryProvider.getRepositoryIdOfRepository(repoUri)); + return new RemoteRepositoryWrapper((HTTPRepository) manager.getRepository(RepositoryProvider.getRepositoryIdOfRepository(repoUri)), configuration); } catch (RepositoryException e) { if (attempts < maxReconnectAttempts) { LOG.warn("Unable to connect to repository {}. Error is: {}. Retrying...", repoUri, e.getMessage()); @@ -198,7 +197,7 @@ private InputStream getConfigFileContent() { return new FileInputStream(configPath); } catch (FileNotFoundException e) { throw new RepositoryCreationException("Unable to find repository configuration file at " + configPath, - e); + e); } } } diff --git a/pom.xml b/pom.xml index 1b470fd7f..4c4ef2386 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 cz.cvut.kbss.jopa - 1.1.0 + 1.1.1 jopa-all pom JOPA @@ -41,7 +41,7 @@ 2.0.6 5.9.2 5.3.1 - 1.9.19 + 1.9.20 1.4.7