kbss-cvut / jopa

Java OWL Persistence API
https://github.com/kbss-cvut/jopa/wiki
GNU Lesser General Public License v3.0
34 stars 15 forks source link

problem with Sesame OntoDriver and class mapping #48

Closed mattWA31 closed 5 years ago

mattWA31 commented 5 years ago

Hello, I have been trying to map my RDF4J server ontology with some models in java for now 3 days, and I still get a lot of errors. Maybe you can help. I have tried to follow the methodology used in the reporting app, but with no more luck.

For example, I get mostly 2 kinds of error :

I have installed an RDF4J server, and imported my simple ontology composed of 2 classes - Person & Book - which look like that :

- <http://www.semanticweb.org/ontologies/library.owl#Person> rdf:type  rdfs:Class
- <http://www.semanticweb.org/ontologies/library.owl#name> rdfs:domain <http://www.semanticweb.org/ontologies/library.owl#Person>
- <http://www.semanticweb.org/ontologies/library.owl#placeOfBirth> rdfs:domain <http://www.semanticweb.org/ontologies/library.owl#Person>

So a person has a name and a placeOfBirth.

Some instances of the Person class are (lib: = http://www.semanticweb.org/ontologies/library.owl#)

- lib:person1 rdf:type lib:Person
- lib:person1 lib:name "Clint Eastwood"
- lib:person1 lib:placeOfBirth "San Francisco"
- lib:person2 rdf:type lib:Person
- lib:person2 lib:name "Marty McFly"
- lib:person2 lib:placeOfBirth "New York"

My Person Model looks like that :

@OWLClass(iri = "http://www.semanticweb.org/ontologies/library.owl#Person")
public class Person implements Serializable {

    @Id
    private URI uri;

    @OWLDataProperty(iri = "http://www.semanticweb.org/ontologies/library.owl#name")
    private String name;

    @OWLDataProperty(iri = "http://www.semanticweb.org/ontologies/library.owl#placeOfBirth")
    private String placeOfBirth;

    public Person(){}

    ... Getters & setters
}

My DAO looks like that :

@Repository
public class PersonDao extends BaseDao<Person> {

    protected PersonDao(){
        super(Person.class);
    }

    public List<Person> findAll(EntityManager em){
        List<Person> toto = em.createNativeQuery("SELECT ?x WHERE { ?x a ?type . }", Person.class).setParameter("type", typeUri)
                .getResultList();

        for(Person p : toto){
            System.out.println(p.getName());
            System.out.println(p.getPlaceOfBirth());
        }

        return toto;
    }

    public Person find(URI uri){
        Objects.requireNonNull(uri);
        final EntityManager em = entityManager();
        try {
            return findByUri(uri, em);
        } finally {
            em.close();
        }
    }

    protected Person findByUri(URI uri, EntityManager em) {
        return em.find(type, uri);
    }
}

My controller is like that :

@RestController
@RequestMapping("/tests")
public class TestController {

    private final PersonService personService;

    @Autowired
    public TestController(PersonService personService){
        this.personService = personService;
    }

   [...]

    @GetMapping(value = "/persons", produces = MediaType.APPLICATION_JSON_VALUE)
    public Collection<Person> getAllPersons(){
        return personService.findAll();
    }

    @GetMapping(value = "/person", produces = MediaType.APPLICATION_JSON_VALUE)
    public Person getPerson(){
        URI uri = 
    URI.create("http://www.semanticweb.org/webatrio/ontologies/library.owl#person1");
        return personService.find(uri);
    }

My PersistenceProvider looks like that :

@Configuration
@PropertySource("classpath:config.properties")
public class PersistenceFactory {

    private static final Map<String, String> DEFAULT_PARAMS = initParams();

    private final Environment environment;

    private EntityManagerFactory emf;

    @Autowired
    public PersistenceFactory(Environment environment) {
        this.environment = environment;
    }

    @Bean
    @Primary
    public EntityManagerFactory getEntityManagerFactory() {
        return emf;
    }

    @PostConstruct
    private void init() {
        // Allow Apache HTTP client used by RDF4J to use a larger connection pool
        // Temporary, should be configurable via JOPA
        System.setProperty("http.maxConnections", "20");
        final Map<String, String> properties = new HashMap<>(DEFAULT_PARAMS);
        properties.put(ONTOLOGY_PHYSICAL_URI_KEY, environment.getProperty(REPOSITORY_URL.toString()));
        properties.put(DATA_SOURCE_CLASS, environment.getProperty(DRIVER.toString()));

        this.emf = Persistence.createEntityManagerFactory("books", properties);
    }

    private static Map<String, String> initParams() {
        final Map<String, String> map = new HashMap<>();
        map.put(OntoDriverProperties.ONTOLOGY_LANGUAGE, "en");
        map.put(SCAN_PACKAGE, "com.example.test.model");
        map.put(JPA_PERSISTENCE_PROVIDER, JOPAPersistenceProvider.class.getName());
        return map;
    }

}

File RestConfig :

@Configuration
@ComponentScan(basePackages = "com.example.test")
public class RestConfig {

    @Bean
    public ObjectMapper objectMapper() {
        final ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        return objectMapper;
    }
}

My service class looks like that :

@Service
public class PersonRepositoryService extends BaseRepositoryService<Person> implements PersonService {

    private final PersonDao personDao;

    @Autowired
    public PersonRepositoryService(PersonDao personDao){
        this.personDao = personDao;
    }

    @Override
    protected GenericDao<Person> getPrimaryDao() {
        return personDao;
    }
}

my config.properties :

repositoryUrl=http://localhost:8080/rdf4j-server/repositories/books2
driver=cz.cvut.kbss.ontodriver.sesame.SesameDataSource

For more information, the querying on the server is happening, the error occur when trying to map the results with the model.

Thank you for the help.

ledsoft commented 5 years ago

Hi, thanks for taking interest in JOPA. I was looking into your setup (assuming the same config structure as in the reporting-tool), I did a couple of tests and all seems to be working for me.

Are you by any chance using Spring Boot? I remember we had some issues with it (see #26), but I was never able to reproduce it. Anyway, this type of error mostly occurs when the scan package (specified in PersistenceFactory in your case) does not allow JOPA to find the entity classes.

mattWA31 commented 5 years ago

Hello,

Thanks for those interesting informations. I have investigated a bit more, and it seems that there is an incompatibility with devtools used with spring boot. I have removed this dependency for the moment.

After testing, it seems to get further (while debugging I have seen that the Person instances are being well created) but I get another error on the Aspect : (version 0.12.1, version 0.9.12)

java.lang.ClassCastException: com.example.test.model.Person cannot be cast to cz.cvut.kbss.jopa.model.BeanListenerAspect$Manageable
    at cz.cvut.kbss.jopa.model.BeanListenerAspect.register(BeanListenerAspect.java:84) ~[jopa-impl-0.9.12.jar:na]
    at cz.cvut.kbss.jopa.sessions.UnitOfWorkImpl.registerEntityWithPersistenceContext(UnitOfWorkImpl.java:622) ~[jopa-impl-0.9.12.jar:na]
    at cz.cvut.kbss.jopa.sessions.UnitOfWorkImpl.registerClone(UnitOfWorkImpl.java:666) ~[jopa-impl-0.9.12.jar:na]

While debugging I went into the BeanListenerAspect.java :

    public void register(Object instance, UnitOfWorkImpl persistenceContext) {
        ((Manageable) instance).setPersistenceContext(persistenceContext);
    }

the instance here is of type Person and cannot be cast to type Manageable. The same error is happening when deregistering (using em.close() for instance)

Java.lang.ClassCastException: com.example.test.model.Person cannot be cast to cz.cvut.kbss.jopa.model.BeanListenerAspect$Manageable
    at cz.cvut.kbss.jopa.model.BeanListenerAspect.deregister(BeanListenerAspect.java:94) ~[jopa-impl-0.9.12.jar:na]
    at cz.cvut.kbss.jopa.sessions.UnitOfWorkImpl.deregisterEntityFromPersistenceContext(UnitOfWorkImpl.java:626) ~[jopa-impl-0.9.12.jar:na]
    at cz.cvut.kbss.jopa.sessions.UnitOfWorkImpl.lambda$detachAllManagedInstances$1(UnitOfWorkImpl.java:235) ~[jopa-impl-0.9.12.jar:na]
    at java.lang.Iterable.forEach(Iterable.java:75) ~[na:1.8.0_191]

Moreover I have tested my same code with version 0.9.1 and all seems to work fine, I get good responses from my controller, for example :

{
  "uri": "http://www.semanticweb.org/ontologies/library.owl#person1",
  "name": "Clint Eastwood",
  "placeOfBirth": "Toulouse"
}
ledsoft commented 5 years ago

Ok, so it indeed was the dev-tools issue. Would it be possible for you to share the pom.xml so that I could investigate it? I was never able to reproduce it originally so we kept an open issue for it here but never really got to fix it.

As for the ClassCastException. This sometimes happen in the IDE (I use IDEA) when running the application from it. It builds the code but does not properly run the AspectJ compiler. The thing is, we use AspectJ inter-type declarations to add the Manageable type to entities to be able to attach a reference to the persistence context they belong to. If you build the project with Maven, all should be fine and the ClassCastException shouldn't occur. But when running from IDE, it sometimes happens. A solution that works for me is to:

  1. Ensure AJC is used for project compilation in the IDE
  2. Rebuild the project with Maven and then try running it from IDE again, it usually helps
mattWA31 commented 5 years ago

Hi

I am not sure it is the devtools that is causing problems. I have packaged my application using mvn package and when I run it afterwards I get :

 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.1.3.RELEASE)

[...]

2019-04-08 00:04:00.801  WARN 16832 --- [           main] cz.cvut.kbss.jopa.loaders.EntityLoader   : No entity classes found in package com.example.test.model

2019-04-08 00:04:01.041  INFO 16832 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2019-04-08 00:04:01.569  INFO 16832 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8081 (http) with context path ''
2019-04-08 00:04:01.579  INFO 16832 --- [           main] com.example.test.TestApplication         : Started TestApplication in 4.777 seconds (JVM running for 5.71)

I'm not sure how to fix this, because my entities are indeed in the package mentioned.

I'm not familiar with AJC compiler, I will look on how to use it.

Here is my pom.xml, but I'm not sure anymore that devtools is the cause :

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.3.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>test</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>test</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>

        <dependency>
            <groupId>cz.cvut.kbss.jopa</groupId>
            <artifactId>jopa-impl</artifactId>
            <version>0.12.1</version>
        </dependency>
        <dependency>
            <groupId>cz.cvut.kbss.jopa</groupId>
            <artifactId>ontodriver-sesame</artifactId>
            <version>0.12.1</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>
ledsoft commented 5 years ago

OK, I think I see the main issue now. You need to use the aspectj-maven-plugin. It is used to process compiled sources and weave the aspects into them (mainly the entity classes). Try adding the following plugin (you can also check out the Wiki):

<plugin>
         <groupId>org.codehaus.mojo</groupId>
         <artifactId>aspectj-maven-plugin</artifactId>
         <configuration>
            <complianceLevel>1.8</complianceLevel>
            <source>1.8</source>
            <target>1.8</target>
            <aspectLibraries>
               <aspectLibrary>
                  <!-- This specifies where the aspects are -->
                  <groupId>cz.cvut.kbss.jopa</groupId>
                  <artifactId>jopa-impl</artifactId>
               </aspectLibrary>
            </aspectLibraries>
         </configuration>
         <dependencies>
            <dependency>
               <groupId>org.aspectj</groupId>
               <artifactId>aspectjtools</artifactId>
            </dependency>
            <dependency>
               <groupId>org.aspectj</groupId>
               <artifactId>aspectjrt</artifactId>
            </dependency>
          </dependencies>
          <executions>
             <execution>
                <phase>process-classes</phase>
                <goals>
                   <!-- use this goal to weave all your main classes -->
                   <goal>compile</goal>
                   <!-- use this goal to weave all your test classes -->
                   <goal>test-compile</goal>
                </goals>
             </execution>
          </executions>
       </plugin>

The warning about No entity classes found in package com.example.test.model is weird, it has been removed from the source quite a while ago. Could this be a conflict in dependencies?

mattWA31 commented 5 years ago

Ok thank you for your help, it is now working like a charm in 0.12.1.

I was missing the aspectJ.

ledsoft commented 5 years ago

Great, glad to hear it!

I'll look again into the dev-tools issue anyway, but I'm closing this one. Feel free to reopen if needed.