5

In my project (Spring Framework + Google App Engine + DataNucleus + JPA) I get the following exception on the server startup:

WARNING: Nestedin org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jpaMappingContext': Invocation of init method failed; 
    nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in ServletContext resource [/WEB-INF/spring/db.xml]: Invocation of init method failed; 
    nested exception is java.lang.NoSuchMethodError: org.datanucleus.metadata.MetaDataUtils.parsePersistenceFiles(Lorg/datanucleus/plugin/PluginManager;Ljava/lang/String;ZLorg/datanucleus/NucleusContext;)[Lorg/datanucleus/metadata/PersistenceFileMetaData;:
java.lang.NoSuchMethodError: org.datanucleus.metadata.MetaDataUtils.parsePersistenceFiles(Lorg/datanucleus/plugin/PluginManager;Ljava/lang/String;ZLorg/datanucleus/NucleusContext;)[Lorg/datanucleus/metadata/PersistenceFileMetaData;
    at org.datanucleus.api.jpa.JPAEntityManagerFactory.<init>(JPAEntityManagerFactory.java:342)
    at org.datanucleus.api.jpa.PersistenceProviderImpl.createEntityManagerFactory(PersistenceProviderImpl.java:91)

Obviously this exception is throwed during persistence.xml parsing. Spring tries to invoke method MetaDataUtils#parsePersistenceFiles(PluginManager,String,NucleusContext,nucCtx), but it is absent. This method is a part of org.datanucleus:datanucleus-core. At first I thought that I have a missing or duplicate dependency somewhere. I've executed

gradle dependencies

carefully scanned output and found nothing suspicious: only a single version of the dependency.
According to the documentation MetaDataUtils has only one parsePersistenceFiles method:

public static PersistenceFileMetaData[] parsePersistenceFiles(
  PluginManager pluginMgr, String persistenceFilename, boolean validate, ClassLoaderResolver clr);

If you are observant, you've probably noticed that it differs in arguments: there is an extra boolean validate argument. Odd thing is that there is no such method in any version of DataNucleus. Why is DataNucleus looking for the method that haven't even existed? I can't get it.

Dora
Please help DataNucleus and me find the missing method!


UPDATE
As Neil Stockton pointed out it's because I'm using inconsistent versions of datanucleus-core and datanucleus-api-jpa. But I don't know the right combination of dependencies. And I'm starting to think, that DataNucleus App Engine Plugin 3.0 is not currently ready for usage.


I want to use DataNucleus App Engine Plugin 3.0 because of this issue (fixed in DataNucleus versions 3.2.6 and 3.3.3) and because I need JPA 2.1 features (fetch groups / entity graphs). The DataNucleus App Engine Plugin 3.0 is compatible with the mentioned versions of DataNucleus, but unreleased. I've checked out the plugin from SVN, packaged it, and added to my project as jar (an identical jar is available for download, if you want to repeat this setup yourself).

build.gradle:

apply plugin: 'java'
apply plugin: 'war'
apply plugin: 'appengine'

dependencies {
    // App Engine
    compile fileTree(dir: 'libs', include: ['*.jar'])  // There is datanucleus-appengine-3.0.0-20140128.jar in libs
    appengineSdk 'com.google.appengine:appengine-java-sdk:1.9.19'
    compile 'com.google.appengine:appengine-api-1.0-sdk:1.9.19'
    
    // persistence
    // App Engine and DataNucleus compatibility:
    // https://code.google.com/p/datanucleus-appengine/wiki/Compatibility
    compile 'org.eclipse.persistence:javax.persistence:2.1.0'
    runtime 'org.datanucleus:datanucleus-core:3.2.15'
    compile 'org.datanucleus:datanucleus-api-jpa:3.1.3'
    compile 'javax.jdo:jdo-api:3.1'
    compile 'org.datanucleus:datanucleus-jodatime:3.2.1'

    // Spring Framework
    compile("org.springframework:spring-webmvc:4.1.6.RELEASE")
    compile ("org.springframework.data:spring-data-jpa:1.8.0.RELEASE")
    providedCompile 'javax.annotation:javax.annotation-api:1.2'

    compile 'com.google.code.gson:gson:2.3.1'
    providedCompile 'org.projectlombok:lombok:1.16.+'
}

appengine {
    downloadSdk = true
    httpAddress = "0.0.0.0"
}

Spring context:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:jpa="http://www.springframework.org/schema/data/jpa"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/data/jpa
        http://www.springframework.org/schema/data/jpa/spring-jpa.xsd"
    default-autowire="byName">
    <jpa:repositories base-package="<my.package.name>.repositories" />

    <!-- The simplest and the most limited form of JPA deployment -->
    <!-- For details see http://docs.spring.io/spring/docs/current/spring-framework-reference/html/orm.html#orm-jpa -->
    <bean id="entityManagerFactory"
        class="org.springframework.orm.jpa.LocalEntityManagerFactoryBean">
        <property name="persistenceUnitName" value="transactions-optional" />
    </bean>

    <bean id="transactionManager"
        class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactory" />
    </bean>

    <tx:annotation-driven transaction-manager="transactionManager" />
</beans>

Persistence unit

<persistence-unit name="transactions-optional">
    <provider>org.datanucleus.api.jpa.PersistenceProviderImpl</provider>
    <properties>
        <property name="datanucleus.NontransactionalRead" value="true" />
        <property name="datanucleus.NontransactionalWrite" value="true" />
        <property name="datanucleus.ConnectionURL" value="appengine" />
        <property name="datanucleus.appengine.datastoreEnableXGTransactions" value="true" />
    </properties>
</persistence-unit>

UPDATE:
If I put datanucleus-api-jpa of another version on CLASSPATH, for example

compile 'org.datanucleus:datanucleus-api-jpa:3.2.2'

I get an exception during enhancement:

java.lang.RuntimeException: Unexpected exception
    at com.google.appengine.tools.enhancer.Enhancer.execute(Enhancer.java:76)
    at com.google.appengine.tools.enhancer.Enhance.<init>(Enhance.java:71)
    ... 1 more
Caused by: java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    ... 5 more
Caused by: org.datanucleus.exceptions.NucleusException: 
    Plugin (Bundle) "org.datanucleus.api.jpa" is already registered. 
    Ensure you dont have multiple JAR versions of the same plugin in the classpath.
    The URL "file:/<GRADLE_HOME>/appengine-sdk/appengine-java-sdk-1.9.19/lib/opt/tools/datanucleus/v2/datanucleus-api-jpa-3.1.3.jar" is already registered, 
    and you are trying to register an identical plugin located at URL "file:/<GRADLE_HOME>/caches/modules-2/files-2.1/org.datanucleus/datanucleus-api-jpa/3.2.2/c24c14634c39b5b9a59dcd379dbb6d93da97f3e7/datanucleus-api-jpa-3.2.2.jar."
    at org.datanucleus.plugin.NonManagedPluginRegistry.registerBundle(NonManagedPluginRegistry.java:541)
    at org.datanucleus.plugin.NonManagedPluginRegistry.registerBundle(NonManagedPluginRegistry.java:395)

From documentation:

The App Engine Java SDK includes version 2.x of the DataNucleus plugin for App Engine. This plugin corresponds to version 3.0 of the DataNucleus Access Platform, which enables you to use the App Engine Datastore via JPA 2.0.
JPA presents a standard interface for interacting with relational databases, but the App Engine datastore is not a relational database. As a result, there are features of JPA that App Engine simply cannot support.

Community
  • 1
  • 1
naXa stands with Ukraine
  • 35,493
  • 19
  • 190
  • 259
  • using inconsistent versions of datanucleus-core and datanucleus-api-jpa. Spring is not making that call as your stack trace shows, called by org.datanucleus.api.jpa.XXX – Neil Stockton Apr 23 '15 at 15:27

1 Answers1

0

The simple fact is that you have to use consistent versions of the various jars. If using Google's "datanucleus-appengine" v3.0 (SVN) then you MUST have DataNucleus project "datanucleus-core", "datanucleus-api-jpa" v3.2.x (or 3.3.x of the datanucleus-api-jpa) and no other versions. If you get some message about

Plugin (Bundle) "org.datanucleus.api.jpa" is already registered.

then you have multiple versions of that plugin in the CLASSPATH and you should FIX your CLASSPATH (just print out what is in the CLASSPATH and it will tell you ... something like System.getProperty("java.class.path")).

In your case you have

file://appengine-sdk/appengine-java-sdk-1.9.19/lib/opt/tools/datanucleus/v2/datanucleus-api-jpa-3.1.3.jar

and

file://caches/modules-2/files-2.1/org.datanucleus/datanucleus-api-jpa/3.2.2/c24c14634c39b5b9a59dcd379dbb6d93da97f3e7/datanucleus-api-jpa-3.2.2.jar

so get rid of the first one

Neil Stockton
  • 11,383
  • 3
  • 34
  • 29