0

I have started learning Helidon MP for a while yet, in tutorials and almost every source code I read based on this micro-service framework, examples are written on H2 database. I couldn't find any example based on MongoDB up to now. I already know about the JPA platform that eclipselink has developed for MongoDB and tried the guides in the JPA/NoSQL Examples. Besides, I could actually run the tests successfully in a simple maven project and creating the EntityManager object using factory directly works fine. But putting it in a Helidon MP project and using CDI for EntityManager like the instructions causes exception when I access http://localhost:8080/person/sahand with curl command. Before I put the related codes, I just found out that when Helidon creates EntityManager for a NoSQL database access, it should use org.eclipse.persistence.eis.EISLogin object while it's creating org.eclipse.persistence.sessions.DatabaseLogin causing a ClassCastException at some point and so on. This is the exception I get:

Caused by: Exception [EclipseLink-7108] (Eclipse Persistence Services - 2.7.7.v20200504-69f2c2b80d): org.eclipse.persistence.exceptions.ValidationException
Exception Description: This operation is not supported for non-relational platforms.
        at org.eclipse.persistence.exceptions.ValidationException.notSupportedForDatasource(ValidationException.java:590)
        at org.eclipse.persistence.internal.sessions.AbstractSession.getLogin(AbstractSession.java:2751)
        at io.helidon.integrations.cdi.eclipselink.CDISEPlatform.initializeExternalTransactionController(CDISEPlatform.java:228)
        at org.eclipse.persistence.internal.sessions.DatabaseSessionImpl.preConnectDatasource(DatabaseSessionImpl.java:851)
        at org.eclipse.persistence.internal.sessions.DatabaseSessionImpl.login(DatabaseSessionImpl.java:823)
        at org.eclipse.persistence.internal.jpa.EntityManagerFactoryProvider.login(EntityManagerFactoryProvider.java:258)
        at org.eclipse.persistence.internal.jpa.EntityManagerSetupImpl.deploy(EntityManagerSetupImpl.java:769)

This is the resource manager for database access:

@Path("/person")
@Dependent
public class PersonResourceManager {
    @PersistenceContext
    private EntityManager em;

    @GET
    @Path("/{name}")
    @Produces("text/plain")
    @Transactional
    public String getResponse(@PathParam("name") String name) {
        Query query = em.createQuery("Select p from PERSON p where p.name LIKE '" + name + "'");
        Person person = (Person) query.getResultList().get(0);
        return String.valueOf(person.getAge()) + "\n";
    }
}

Here is the Entity I'm trying to read from a MongoDB collection which is already created:

@Entity(name = "PERSON")
@NoSql(dataFormat = DataFormatType.MAPPED)
public class Person implements Serializable {
    @Id
    @GeneratedValue
    @Field(name = "_id")
    private String id;

    @Basic
    private String name;

    @Basic
    private int age;

    @Version
    private long version;

    @Deprecated
    protected Person() {}
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getId() {return id;}
    public String getName() {return name;}
    public void setName(String name) {this.name = name;}
    public int getAge() {return age;}
    public void setAge(int age) {this.age = age;}
}

These are the maven dependencies related to database resolved in the project:

<dependency>
    <groupId>javax.transaction</groupId>
    <artifactId>javax.transaction-api</artifactId>
    <version>1.2</version>
    <scope>provided</scope>
</dependency>
<dependency>
    <groupId>javax.resource</groupId>
    <artifactId>connector-api</artifactId>
    <version>1.5</version>
    <scope>provided</scope>
</dependency>
<dependency>
    <groupId>io.helidon.integrations.cdi</groupId>
    <artifactId>helidon-integrations-cdi-eclipselink</artifactId>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>io.helidon.integrations.cdi</groupId>
    <artifactId>helidon-integrations-cdi-jpa</artifactId>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>io.helidon.integrations.cdi</groupId>
    <artifactId>helidon-integrations-cdi-jta-weld</artifactId>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>io.helidon.integrations.cdi</groupId>
    <artifactId>helidon-integrations-cdi-datasource-hikaricp</artifactId>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>jakarta.persistence</groupId>
    <artifactId>jakarta.persistence-api</artifactId>
    <scope>provided</scope>
</dependency>
<dependency>
    <groupId>org.mongodb</groupId>
    <artifactId>mongo-java-driver</artifactId>
    <version>3.12.6</version>
</dependency>
<dependency>
    <groupId>org.eclipse.persistence</groupId>
    <artifactId>eclipselink</artifactId>
    <version>2.7.7</version>
</dependency>
<dependency>
    <groupId>org.eclipse.persistence</groupId>
    <artifactId>org.eclipse.persistence.nosql</artifactId>
    <version>2.7.7</version>
</dependency>

Unit definition in persistence.xml file:

    <persistence-unit name="mongodb" transaction-type="RESOURCE_LOCAL">
        <class>sahand.example.helidon.Person</class>
        <properties>
            <property name="eclipselink.target-database" value="org.eclipse.persistence.nosql.adapters.mongo.MongoPlatform"/>
            <property name="eclipselink.nosql.connection-spec" value="org.eclipse.persistence.nosql.adapters.mongo.MongoConnectionSpec"/>
            <property name="eclipselink.nosql.property.mongo.port" value="27017"/>
            <property name="eclipselink.nosql.property.mongo.host" value="localhost"/>
            <property name="eclipselink.nosql.property.mongo.db" value="jpa-nosql-demo"/>
            <property name="eclipselink.logging.level" value="FINEST"/>
        </properties>
    </persistence-unit>

And finally a part of application.yaml that I guess is correct. However, I'm kind of sure that exception happens before even reading this configuration.

javax:
  sql:
    DataSource:
      person:
        dataSourceClassName: othatrg.eclipse.persistence.nosql.adapters.mongo.MongoPlatform
        dataSource:
          url: mongodb://localhost:27017
          user: sample_user
          password: "samplePass"

I would be really grateful if someone could help me with this. Although it's possible to create every EntityManager object by factory, I feel like this solution is messy. Either I'm using wrong dependency or something else I don't know about. This is also the instruction I followed for Helidon and JPA.

  • Why you ask your question twice? – Jens Aug 08 '20 at 12:50
  • A problem in network caused the firts upload to fail (while it didn't) – sahand_77.s Aug 09 '20 at 04:16
  • Looks like you've set your persistence unit to be resource local, but the stack trace shows you have somehow specified using an ExternalTransactionController, which implies JTA - the Helidon docs you references all show a JTA persistence unit where it controls the datasource and the transaction. You would have to see how their datasource can fit with mongoDB, or use a fully resource local persistence unit (where you create the EntityManager instances the transactional demarcation entirely yourself) – Chris Aug 10 '20 at 15:30
  • Thanks a lot for your help. I've set the transaction type in persistence.xml file to JTA (as Helidon tutorial says). This causes the tests I got from eclipselink JPA/NoSQL instruction to fail but that's okay since it can be fixed if the main problem is solved. Despite the modification, the problem still remains. I'm assuming that transaction configurations and annotations in the java codes are correct. Can the problem be from dependencies? You can download my full [pom.xml](https://file.io/ioL5OKk6advb) file from the link. – sahand_77.s Aug 11 '20 at 06:17
  • You may be running into a Helidon bug. The code that is throwing the exception in Eclipselink is here: https://github.com/eclipse-ee4j/eclipselink/blob/69f2c2b80da2cb94974b1718bd5a30a6819ff07b/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/internal/sessions/AbstractSession.java#L2746-L2753 Preliminary guess: it looks like Helidon is calling `getLogin()` instead of `getDataSourceLogin()`. I'll do some more research and update this question. – Laird Nelson Aug 13 '20 at 15:58
  • This problem is solved in the new release 2.0.2 – sahand_77.s Sep 04 '20 at 11:41

1 Answers1

0

This was a bug in Helidon in the sense that the JPA subsystem in Helidon was calling the relational-database-specific method Session#getLogin() instead of the more apparently general-purpose Session#getDatasourceLogin() method.

The fix should be available in Helidon 2.0.2. While it is not guaranteed to fix this issue, it is the right thing for Helidon to do nonetheless.

Laird Nelson
  • 15,321
  • 19
  • 73
  • 127