diff --git a/TODO.md b/TODO.md index f3fa28748..c947e310d 100644 --- a/TODO.md +++ b/TODO.md @@ -19,6 +19,8 @@ - This could be overwritten with a descriptor passed to EM - [ ] Add support for `orphanRemoval` attribute in object properties - [ ] Modify OntoDriver API to support Fetch joins +- [ ] Allow static (annotation, attribute of `@OWLDataProperty`) specification of language of String attributes. + This can be overridden on EM operation level (in descriptor). ## Low Priority @@ -37,3 +39,5 @@ - [ ] Research whether we could replace aspectj with cglib-generated proxies ### Currently in Progress + +- [ ] Allow specification of language for string attributes of an entity. diff --git a/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/descriptors/Descriptor.java b/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/descriptors/Descriptor.java index d2dd27063..c49110618 100644 --- a/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/descriptors/Descriptor.java +++ b/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/descriptors/Descriptor.java @@ -18,22 +18,19 @@ import java.lang.reflect.Field; import java.net.URI; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; +import java.util.*; /** * Defines base descriptor, which is used to specify context information for entities and their fields. *

* The descriptor hierarchy is a classical Composite pattern. - * - * @author ledvima1 */ public abstract class Descriptor { protected final URI context; + protected String language; + protected Descriptor() { this(null); } @@ -53,6 +50,26 @@ public URI getContext() { return context; } + /** + * Gets the language set for this descriptor. + * + * @return Language tag (e.g. en, cs). + */ + public Optional getLanguage() { + return Optional.ofNullable(language); + } + + /** + * Sets language tag of this descriptor. + *

+ * Applies to any possible sub-descriptors as well. + * + * @param languageTag The language tag to use, possibly null, meaning no language preference should be used + */ + public void setLanguage(String languageTag) { + this.language = languageTag; + } + /** * Gets attribute descriptors specified in this descriptor. * @@ -88,6 +105,17 @@ public URI getContext() { */ public abstract void addAttributeContext(Field attribute, URI context); + /** + * Sets language to be used when working (retrieving, persisting) with values of the specified attribute. + *

+ * Note that setting language in this manner will not have any effect on descriptors of the + * specified attribute previously retrieved from this descriptor. + * + * @param attribute The attribute concerned + * @param languageTag Language tag to use, possibly {@code null} + */ + public abstract void setAttributeLanguage(Field attribute, String languageTag); + /** * Gets all contexts present in this descriptor. *

@@ -100,7 +128,7 @@ public URI getContext() { public Set getAllContexts() { Set contexts = new HashSet<>(); contexts = getContextsInternal(contexts, new HashSet<>()); - return contexts != null ? contexts : Collections.emptySet(); + return contexts != null ? contexts : Collections.emptySet(); } /** diff --git a/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/descriptors/EntityDescriptor.java b/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/descriptors/EntityDescriptor.java index dec889b93..2b2d403bd 100644 --- a/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/descriptors/EntityDescriptor.java +++ b/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/descriptors/EntityDescriptor.java @@ -59,11 +59,26 @@ public void addAttributeContext(Field attribute, URI context) { fieldDescriptors.put(attribute, new FieldDescriptor(context, attribute)); } + @Override + public void setLanguage(String languageTag) { + super.setLanguage(languageTag); + fieldDescriptors.values().forEach(d -> d.setLanguage(languageTag)); + } + + @Override + public void setAttributeLanguage(Field attribute, String languageTag) { + Objects.requireNonNull(attribute); + + fieldDescriptors.putIfAbsent(attribute, new FieldDescriptor(null, attribute)); + fieldDescriptors.get(attribute).setLanguage(languageTag); + } + @Override public Descriptor getAttributeDescriptor(FieldSpecification attribute) { Descriptor d = getFieldDescriptor(attribute.getJavaField()); if (d == null) { d = createDescriptor(attribute, context); + d.setLanguage(language); } return d; } diff --git a/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/descriptors/FieldDescriptor.java b/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/descriptors/FieldDescriptor.java index c23d016ff..dc0e9c30e 100644 --- a/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/descriptors/FieldDescriptor.java +++ b/jopa-api/src/main/java/cz/cvut/kbss/jopa/model/descriptors/FieldDescriptor.java @@ -59,6 +59,14 @@ public void addAttributeContext(Field attribute, URI context) { // Do nothing } + /** + * Use {@link #setLanguage(String)} instead. + */ + @Override + public void setAttributeLanguage(Field attribute, String languageTag) { + // Do nothing + } + private Descriptor getFieldDescriptor(Field field) { if (this.field.equals(field)) { return this; @@ -97,8 +105,6 @@ public boolean equals(Object obj) { if (getClass() != obj.getClass()) return false; FieldDescriptor other = (FieldDescriptor) obj; - if (!field.equals(other.field)) - return false; - return true; + return field.equals(other.field); } } diff --git a/jopa-api/src/test/java/cz/cvut/kbss/jopa/model/descriptors/EntityDescriptorTest.java b/jopa-api/src/test/java/cz/cvut/kbss/jopa/model/descriptors/EntityDescriptorTest.java new file mode 100644 index 000000000..e3c3b65d9 --- /dev/null +++ b/jopa-api/src/test/java/cz/cvut/kbss/jopa/model/descriptors/EntityDescriptorTest.java @@ -0,0 +1,122 @@ +package cz.cvut.kbss.jopa.model.descriptors; + +import cz.cvut.kbss.jopa.model.annotations.OWLDataProperty; +import cz.cvut.kbss.jopa.model.metamodel.Attribute; +import org.junit.Test; + +import java.lang.reflect.Field; +import java.net.URI; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class EntityDescriptorTest { + + private static final URI CONTEXT_ONE = URI.create("http://krizik.felk.cvut.cz/ontologies/jopa/contextOne"); + private static final URI CONTEXT_TWO = URI.create("http://krizik.felk.cvut.cz/ontologies/jopa/contextTwo"); + private static final String LANG = "en"; + + @Test + public void fieldDescriptorByDefaultInheritsEntityContext() throws Exception { + final EntityDescriptor descriptor = new EntityDescriptor(CONTEXT_ONE); + final Attribute att = mock(Attribute.class); + when(att.getJavaField()).thenReturn(TestClass.stringAttField()); + final Descriptor result = descriptor.getAttributeDescriptor(att); + assertEquals(CONTEXT_ONE, result.getContext()); + } + + @Test + public void fieldDescriptorHasItsOwnContextWhenItIsSetForIt() throws Exception { + final EntityDescriptor descriptor = new EntityDescriptor(CONTEXT_ONE); + final Attribute att = mock(Attribute.class); + when(att.getJavaField()).thenReturn(TestClass.stringAttField()); + descriptor.addAttributeContext(att.getJavaField(), CONTEXT_TWO); + + final Descriptor result = descriptor.getAttributeDescriptor(att); + assertEquals(CONTEXT_TWO, result.getContext()); + } + + @Test + public void fieldDescriptorByDefaultInheritsEntityLanguageTag() throws Exception { + final EntityDescriptor descriptor = new EntityDescriptor(); + final Attribute att = mock(Attribute.class); + when(att.getJavaField()).thenReturn(TestClass.stringAttField()); + descriptor.setLanguage(LANG); + assertTrue(descriptor.getLanguage().isPresent()); + + final Descriptor result = descriptor.getAttributeDescriptor(att); + assertTrue(result.getLanguage().isPresent()); + assertEquals(LANG, result.getLanguage().get()); + } + + @Test + public void fieldDescriptorInheritsChangeOfLanguageTagFromEntityDescriptor() throws Exception { + final EntityDescriptor descriptor = new EntityDescriptor(); + final Attribute att = mock(Attribute.class); + when(att.getJavaField()).thenReturn(TestClass.stringAttField()); + descriptor.setLanguage(LANG); + final String newLang = "cs"; + descriptor.setLanguage(newLang); + final Descriptor result = descriptor.getAttributeDescriptor(att); + assertTrue(result.getLanguage().isPresent()); + assertEquals(newLang, result.getLanguage().get()); + } + + @Test + public void fieldDescriptorHasLanguageSetToItThroughEntityDescriptor() throws Exception { + final EntityDescriptor descriptor = new EntityDescriptor(); + final Attribute att = mock(Attribute.class); + when(att.getJavaField()).thenReturn(TestClass.stringAttField()); + descriptor.setLanguage(LANG); + final String newLang = "cs"; + descriptor.setAttributeLanguage(att.getJavaField(), newLang); + + final Descriptor result = descriptor.getAttributeDescriptor(att); + assertTrue(result.getLanguage().isPresent()); + assertEquals(newLang, result.getLanguage().get()); + } + + @Test + public void twoEntityDescriptorsAreEqualWhenTheirFieldDescriptorsHaveTheSameContexts() throws Exception { + final EntityDescriptor dOne = new EntityDescriptor(CONTEXT_ONE); + final EntityDescriptor dTwo = new EntityDescriptor(CONTEXT_ONE); + dOne.addAttributeContext(TestClass.stringAttField(), CONTEXT_TWO); + dTwo.addAttributeContext(TestClass.stringAttField(), CONTEXT_TWO); + dOne.addAttributeDescriptor(TestClass.intAttField(), new FieldDescriptor(CONTEXT_ONE, TestClass.intAttField())); + dTwo.addAttributeDescriptor(TestClass.intAttField(), new FieldDescriptor(CONTEXT_ONE, TestClass.intAttField())); + + assertTrue(dOne.equals(dTwo)); + assertTrue(dTwo.equals(dOne)); + assertEquals(dOne.hashCode(), dTwo.hashCode()); + } + + @Test + public void twoEntityDescriptorsAreNotEqualWhenTheyDifferInFieldContext() throws Exception { + final EntityDescriptor dOne = new EntityDescriptor(CONTEXT_ONE); + final EntityDescriptor dTwo = new EntityDescriptor(CONTEXT_ONE); + dOne.addAttributeContext(TestClass.stringAttField(), CONTEXT_TWO); + dTwo.addAttributeContext(TestClass.stringAttField(), CONTEXT_ONE); + + assertFalse(dOne.equals(dTwo)); + assertNotEquals(dOne.hashCode(), dTwo.hashCode()); + } + + @SuppressWarnings("unused") + private static class TestClass { + + @OWLDataProperty(iri = "http://krizik.felk.cvut.cz/ontologies/jopa/attributes/stringAtt") + private String stringAtt; + + @OWLDataProperty(iri = "http://krizik.felk.cvut.cz/ontologies/jopa/attributes/intAtt") + private Integer intAtt; + + private static Field stringAttField() throws NoSuchFieldException { + return TestClass.class.getDeclaredField("stringAtt"); + } + + private static Field intAttField() throws NoSuchFieldException { + return TestClass.class.getDeclaredField("intAtt"); + } + } +} \ No newline at end of file diff --git a/jopa-integration-tests-owlapi/src/test/java/cz/cvut/kbss/jopa/test/environment/OwlapiDataPersist.java b/jopa-integration-tests-owlapi/src/test/java/cz/cvut/kbss/jopa/test/environment/OwlapiDataPersist.java index b1f3a21c9..23f8e4bef 100644 --- a/jopa-integration-tests-owlapi/src/test/java/cz/cvut/kbss/jopa/test/environment/OwlapiDataPersist.java +++ b/jopa-integration-tests-owlapi/src/test/java/cz/cvut/kbss/jopa/test/environment/OwlapiDataPersist.java @@ -1,11 +1,11 @@ /** * Copyright (C) 2016 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 @@ -16,7 +16,6 @@ import cz.cvut.kbss.jopa.CommonVocabulary; import cz.cvut.kbss.jopa.model.EntityManager; -import cz.cvut.kbss.jopa.test.environment.Triple; import cz.cvut.kbss.ontodriver.owlapi.util.OwlapiUtils; import org.semanticweb.owlapi.model.*; @@ -41,11 +40,11 @@ public void persistTestData(Collection data, EntityManager em) throws Ex axiom = new AddAxiom(ontology, df.getOWLObjectPropertyAssertionAxiom(op, ind, obj)); } else if (t.getProperty().toString().equals(CommonVocabulary.RDFS_LABEL)) { final OWLAnnotationProperty ap = df.getOWLAnnotationProperty(IRI.create(t.getProperty())); - final OWLLiteral value = OwlapiUtils.createOWLLiteralFromValue(t.getValue(), df, "en"); + final OWLLiteral value = OwlapiUtils.createOWLLiteralFromValue(t.getValue(), df, t.getLanguage()); axiom = new AddAxiom(ontology, df.getOWLAnnotationAssertionAxiom(ap, ind.getIRI(), value)); } else { final OWLDataProperty dp = df.getOWLDataProperty(IRI.create(t.getProperty())); - final OWLLiteral value = OwlapiUtils.createOWLLiteralFromValue(t.getValue(), df, "en"); + final OWLLiteral value = OwlapiUtils.createOWLLiteralFromValue(t.getValue(), df, t.getLanguage()); axiom = new AddAxiom(ontology, df.getOWLDataPropertyAssertionAxiom(dp, ind, value)); } manager.applyChange(axiom); diff --git a/jopa-integration-tests-sesame/src/test/java/cz/cvut/kbss/jopa/test/environment/SesameDataPersist.java b/jopa-integration-tests-sesame/src/test/java/cz/cvut/kbss/jopa/test/environment/SesameDataPersist.java index c3856c4e5..f31d7be08 100644 --- a/jopa-integration-tests-sesame/src/test/java/cz/cvut/kbss/jopa/test/environment/SesameDataPersist.java +++ b/jopa-integration-tests-sesame/src/test/java/cz/cvut/kbss/jopa/test/environment/SesameDataPersist.java @@ -1,11 +1,11 @@ /** * Copyright (C) 2016 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 @@ -27,8 +27,7 @@ public class SesameDataPersist { public void persistTestData(Collection data, EntityManager em) throws Exception { final Repository repository = em.unwrap(Repository.class); - final RepositoryConnection connection = repository.getConnection(); - try { + try (final RepositoryConnection connection = repository.getConnection()) { final ValueFactory vf = connection.getValueFactory(); connection.begin(); for (Triple t : data) { @@ -37,13 +36,11 @@ public void persistTestData(Collection data, EntityManager em) throws Ex vf.createIRI(t.getValue().toString())); } else { connection.add(vf.createIRI(t.getSubject().toString()), vf.createIRI(t.getProperty().toString()), - SesameUtils.createDataPropertyLiteral(t.getValue(), "en", vf)); + SesameUtils.createDataPropertyLiteral(t.getValue(), t.getLanguage(), vf)); } } connection.commit(); - } finally { - connection.close(); } } } diff --git a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/environment/Triple.java b/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/environment/Triple.java index 58ab8ac41..6868a5f7e 100644 --- a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/environment/Triple.java +++ b/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/environment/Triple.java @@ -1,11 +1,11 @@ /** * Copyright (C) 2016 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 @@ -19,14 +19,23 @@ public class Triple { - private URI subject; - private URI property; - private Object value; + private final URI subject; + private final URI property; + private final Object value; + private final String language; public Triple(URI subject, URI property, Object value) { this.subject = Objects.requireNonNull(subject); this.property = Objects.requireNonNull(property); this.value = Objects.requireNonNull(value); + this.language = "en"; + } + + public Triple(URI subject, URI property, Object value, String language) { + this.subject = Objects.requireNonNull(subject); + this.property = Objects.requireNonNull(property); + this.value = Objects.requireNonNull(value); + this.language = Objects.requireNonNull(language); } public URI getSubject() { @@ -41,6 +50,10 @@ public Object getValue() { return value; } + public String getLanguage() { + return language; + } + @Override public boolean equals(Object o) { if (this == o) return true; @@ -48,9 +61,8 @@ public boolean equals(Object o) { Triple triple = (Triple) o; - if (!subject.equals(triple.subject)) return false; - if (!property.equals(triple.property)) return false; - return value.equals(triple.value); + return subject.equals(triple.subject) && property.equals(triple.property) && language.equals(triple.language) && + value.equals(triple.value); } @@ -59,6 +71,7 @@ public int hashCode() { int result = subject.hashCode(); result = 31 * result + property.hashCode(); result = 31 * result + value.hashCode(); + result = 31 * result + language.hashCode(); return result; } } diff --git a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/runner/RetrieveOperationsRunner.java b/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/runner/RetrieveOperationsRunner.java index 7c44044c2..68a6e9628 100644 --- a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/runner/RetrieveOperationsRunner.java +++ b/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/runner/RetrieveOperationsRunner.java @@ -14,8 +14,11 @@ */ package cz.cvut.kbss.jopa.test.runner; +import cz.cvut.kbss.jopa.model.descriptors.Descriptor; +import cz.cvut.kbss.jopa.model.descriptors.EntityDescriptor; import cz.cvut.kbss.jopa.test.*; import cz.cvut.kbss.jopa.test.environment.Generators; +import cz.cvut.kbss.jopa.test.environment.Triple; import org.junit.Test; import org.slf4j.Logger; @@ -198,4 +201,23 @@ public void retrieveLoadsUnmappedPropertiesTogetherWithObjectPropertyValues() { assertEquals(v.getThings().size(), result.getThings().size()); result.getThings().forEach(t -> assertTrue(expectedUris.contains(t.getUri()))); } + + @Test + public void retrieveGetsStringAttributeWithCorrectLanguageWhenItIsSpecifiedInDescriptor() throws Exception { + this.em = getEntityManager("retrieveGetsStringAttributeWithCorrectLanguageWhenItIsSpecifiedInDescriptor", + false); + persist(entityA); + final String value = "v cestine"; + final String lang = "cs"; + persistTestData(Collections + .singleton(new Triple(entityA.getUri(), URI.create(Vocabulary.P_A_STRING_ATTRIBUTE), value, lang)), em); + + final Descriptor descriptor = new EntityDescriptor(); + descriptor.setLanguage(lang); + + final OWLClassA result = em.find(OWLClassA.class, entityA.getUri()); + assertNotNull(result); + assertEquals(value, result.getStringAttribute()); + assertEquals(entityA.getTypes(), result.getTypes()); + } }