61662

How can I run DataNucleus Bytecode Enhancer from SBT?

I've put together a proof of concept which aims to provide a skeleton SBT multimodule project which utilizes DataNucleus JDO Enhancer with mixed Java and Scala sources.

The difficulty appears when I try to enhance persistence classes from SBT. Apparently, I'm not passing the correct classpath when calling Fork.java.fork(...) from SBT.

<hr>

See also this question: How can SBT generate metamodel classes from model classes using DataNucleus?

<hr> Exception in thread "main" java.lang.NoClassDefFoundError: Could not initialize class org.datanucleus.util.Localiser at org.datanucleus.metadata.MetaDataManagerImpl.loadPersistenceUnit(MetaDataManagerImpl.java:1104) at org.datanucleus.enhancer.DataNucleusEnhancer.getFileMetadataForInput(DataNucleusEnhancer.java:768) at org.datanucleus.enhancer.DataNucleusEnhancer.enhance(DataNucleusEnhancer.java:488) at org.datanucleus.api.jdo.JDOEnhancer.enhance(JDOEnhancer.java:125) at javax.jdo.Enhancer.run(Enhancer.java:196) at javax.jdo.Enhancer.main(Enhancer.java:130) [info] Compiling 2 Java sources to /home/rgomes/workspace/poc-scala-datanucleus/model/target/scala-2.11/klasses... java.lang.IllegalStateException: errno = 1 at $54321831a5683ffa07b5$.runner(build.sbt:230) at $54321831a5683ffa07b5$$anonfun$model$7.apply(build.sbt:259) at $54321831a5683ffa07b5$$anonfun$model$7.apply(build.sbt:258) at scala.Function1$$anonfun$compose$1.apply(Function1.scala:47) at sbt.$tilde$greater$$anonfun$$u2219$1.apply(TypeFunctions.scala:40) at sbt.std.Transform$$anon$4.work(System.scala:63) at sbt.Execute$$anonfun$submit$1$$anonfun$apply$1.apply(Execute.scala:226) at sbt.Execute$$anonfun$submit$1$$anonfun$apply$1.apply(Execute.scala:226) at sbt.ErrorHandling$.wideConvert(ErrorHandling.scala:17) at sbt.Execute.work(Execute.scala:235) at sbt.Execute$$anonfun$submit$1.apply(Execute.scala:226) at sbt.Execute$$anonfun$submit$1.apply(Execute.scala:226) at sbt.ConcurrentRestrictions$$anon$4$$anonfun$1.apply(ConcurrentRestrictions.scala:159) at sbt.CompletionService$$anon$2.call(CompletionService.scala:28)

For the sake of completeness and information, below you can see a java command line generated by SBT which can be executed by hand on a separate window, for example. It just works fine.

$ java -cp /home/rgomes/.ivy2/cache/org.scala-lang/scala-library/jars/scala-library-2.11.6.jar:/home/rgomes/.ivy2/cache/com.google.code.gson/gson/jars/gson-2.3.1.jar:/home/rgomes/.ivy2/cache/javax.jdo/jdo-api/jars/jdo-api-3.0.jar:/home/rgomes/.ivy2/cache/javax.transaction/transaction-api/jars/transaction-api-1.1.jar:/home/rgomes/.ivy2/cache/org.datanucleus/datanucleus-core/jars/datanucleus-core-4.0.4.jar:/home/rgomes/.ivy2/cache/org.datanucleus/datanucleus-api-jdo/jars/datanucleus-api-jdo-4.0.4.jar:/home/rgomes/.ivy2/cache/org.datanucleus/datanucleus-jdo-query/jars/datanucleus-jdo-query-4.0.4.jar:/home/rgomes/.ivy2/cache/org.datanucleus/datanucleus-rdbms/jars/datanucleus-rdbms-4.0.4.jar:/home/rgomes/.ivy2/cache/com.h2database/h2/jars/h2-1.4.185.jar:/home/rgomes/.ivy2/cache/org.postgresql/postgresql/jars/postgresql-9.4-1200-jdbc41.jar:/home/rgomes/.ivy2/cache/com.github.dblock.waffle/waffle-jna/jars/waffle-jna-1.7.jar:/home/rgomes/.ivy2/cache/net.java.dev.jna/jna/jars/jna-4.1.0.jar:/home/rgomes/.ivy2/cache/net.java.dev.jna/jna-platform/jars/jna-platform-4.1.0.jar:/home/rgomes/.ivy2/cache/org.slf4j/slf4j-simple/jars/slf4j-simple-1.7.7.jar:/home/rgomes/.ivy2/cache/org.slf4j/slf4j-api/jars/slf4j-api-1.7.7.jar:/home/rgomes/workspace/poc-scala-datanucleus/model/src/main/resources:/home/rgomes/workspace/poc-scala-datanucleus/model/target/scala-2.11/klasses javax.jdo.Enhancer -v -pu persistence-h2 -d /home/rgomes/workspace/poc-scala-datanucleus/model/target/scala-2.11/classes May 13, 2015 3:30:07 PM org.datanucleus.enhancer.ClassEnhancerImpl save INFO: Writing class file "/home/rgomes/workspace/poc-scala-datanucleus/model/target/scala-2.11/classes/model/AbstractModel.class" with enhanced definition May 13, 2015 3:30:07 PM org.datanucleus.enhancer.DataNucleusEnhancer addMessage INFO: ENHANCED (Persistable) : model.AbstractModel May 13, 2015 3:30:07 PM org.datanucleus.enhancer.ClassEnhancerImpl save INFO: Writing class file "/home/rgomes/workspace/poc-scala-datanucleus/model/target/scala-2.11/classes/model/Identifier.class" with enhanced definition May 13, 2015 3:30:07 PM org.datanucleus.enhancer.DataNucleusEnhancer addMessage INFO: ENHANCED (Persistable) : model.Identifier May 13, 2015 3:30:07 PM org.datanucleus.enhancer.DataNucleusEnhancer addMessage INFO: DataNucleus Enhancer completed with success for 2 classes. Timings : input=112 ms, enhance=102 ms, total=214 ms. Consult the log for full details Enhancer Processing -v. Enhancer adding Persistence Unit persistence-h2. Enhancer processing output directory /home/rgomes/workspace/poc-scala-datanucleus/model/target/scala-2.11/classes. Enhancer found JDOEnhancer of class org.datanucleus.api.jdo.JDOEnhancer. Enhancer property key:VendorName value:DataNucleus. Enhancer property key:VersionNumber value:4.0.4. Enhancer property key:API value:JDO. Enhancer enhanced 2 classes.

Below you can see some debugging information which is passed to Fork.java.fork(...):

============================================================= mainClass=javax.jdo.Enhancer args=-v -pu persistence-h2 -d /home/rgomes/workspace/poc-scala-datanucleus/model/target/scala-2.11/classes javaHome=None cwd=/home/rgomes/workspace/poc-scala-datanucleus/model/target/scala-2.11/classes runJVMOptions= bootJars --------------------------------------------- /home/rgomes/.ivy2/cache/org.scala-lang/scala-library/jars/scala-library-2.11.6.jar /home/rgomes/.ivy2/cache/com.google.code.gson/gson/jars/gson-2.3.1.jar /home/rgomes/.ivy2/cache/javax.jdo/jdo-api/jars/jdo-api-3.0.jar /home/rgomes/.ivy2/cache/javax.transaction/transaction-api/jars/transaction-api-1.1.jar /home/rgomes/.ivy2/cache/org.datanucleus/datanucleus-core/jars/datanucleus-core-4.0.4.jar /home/rgomes/.ivy2/cache/org.datanucleus/datanucleus-api-jdo/jars/datanucleus-api-jdo-4.0.4.jar /home/rgomes/.ivy2/cache/org.datanucleus/datanucleus-jdo-query/jars/datanucleus-jdo-query-4.0.4.jar /home/rgomes/.ivy2/cache/org.datanucleus/datanucleus-rdbms/jars/datanucleus-rdbms-4.0.4.jar /home/rgomes/.ivy2/cache/com.h2database/h2/jars/h2-1.4.185.jar /home/rgomes/.ivy2/cache/org.postgresql/postgresql/jars/postgresql-9.4-1200-jdbc41.jar /home/rgomes/.ivy2/cache/com.github.dblock.waffle/waffle-jna/jars/waffle-jna-1.7.jar /home/rgomes/.ivy2/cache/net.java.dev.jna/jna/jars/jna-4.1.0.jar /home/rgomes/.ivy2/cache/net.java.dev.jna/jna-platform/jars/jna-platform-4.1.0.jar /home/rgomes/.ivy2/cache/org.slf4j/slf4j-simple/jars/slf4j-simple-1.7.7.jar /home/rgomes/.ivy2/cache/org.slf4j/slf4j-api/jars/slf4j-api-1.7.7.jar /home/rgomes/workspace/poc-scala-datanucleus/model/src/main/resources /home/rgomes/workspace/poc-scala-datanucleus/model/target/scala-2.11/klasses envVars ---------------------------------------------- =============================================================

The project is available in github for your convenience at https://github.com/frgomes/poc-scala-datanucleus

Just download it and type

./sbt compile

Any help is immensely appreciated. Thanks

Answer1:

You can either use java.lang.ProcessBuilder or sbt.Fork.

See below a generic javaRunner you can add to your build.sbt which employs java.lang.ProcessBuilder.

See also a generic sbtRunner you can add to your build.sbt which employs sbt.Fork. Thanks to @dwijnand for providing insightful information for making sbtRunner work as expected.

def javaRunner(mainClass: String, args: Seq[String], classpath: Seq[File], cwd: File, javaHome: Option[File] = None, runJVMOptions: Seq[String] = Nil, envVars: Map[String, String] = Map.empty, connectInput: Boolean = false, outputStrategy: Option[OutputStrategy] = Some(StdoutOutput)): Seq[File] = { val java_ : String = javaHome.fold("") { p => p.absolutePath + "/bin/" } + "java" val jvm_ : Seq[String] = runJVMOptions.map(p => p.toString) val cp_ : Seq[String] = classpath.map(p => p.absolutePath) val env_ = envVars.map({ case (k,v) => s"${k}=${v}" }) val xcmd_ : Seq[String] = Seq(java_) ++ jvm_ ++ Seq("-cp", cp_.mkString(java.io.File.pathSeparator), mainClass) ++ args println("=============================================================") println(xcmd_.mkString(" ")) println("=============================================================") println("") IO.createDirectory(cwd) import scala.collection.JavaConverters._ val cmd = xcmd_.asJava val pb = new java.lang.ProcessBuilder(cmd) pb.directory(cwd) pb.inheritIO val process = pb.start() def cancel() = { println("Run canceled.") process.destroy() 1 } val errno = try process.waitFor catch { case e: InterruptedException => cancel() } if(errno==0) { if (args.contains("-v")) cwd.list.foreach(f => println(f)) cwd.listFiles } else { throw new IllegalStateException(s"errno = ${errno}") } } def sbtRunner(mainClass: String, args: Seq[String], classpath: Seq[File], cwd: File, javaHome: Option[File] = None, runJVMOptions: Seq[String] = Nil, envVars: Map[String, String] = Map.empty, connectInput: Boolean = false, outputStrategy: Option[OutputStrategy] = Some(StdoutOutput)): Seq[File] = { val args_ = args.map(p => p.toString) val java_ = javaHome.fold("None") { p => p.absolutePath } val cp_ = classpath.map(p => p.absolutePath) val jvm_ = runJVMOptions.map(p => p.toString) ++ Seq("-cp", cp_.mkString(java.io.File.pathSeparator)) val env_ = envVars.map({ case (k,v) => s"${k}=${v}" }) def dump: String = s""" |mainClass=${mainClass} |args=${args_.mkString(" ")} |javaHome=${java_} |cwd=${cwd.absolutePath} |runJVMOptions=${jvm_.mkString(" ")} |classpath -------------------------------------------- |${cp_.mkString("\n")} |envVars ---------------------------------------------- |${env_.mkString("\n")} """.stripMargin def cmd: String = s"""java ${jvm_.mkString(" ")} ${mainClass} ${args_.mkString(" ")}""" println("=============================================================") println(dump) println("=============================================================") println(cmd) println("=============================================================") println("") IO.createDirectory(cwd) val options = ForkOptions( javaHome = javaHome, outputStrategy = outputStrategy, bootJars = Seq.empty, workingDirectory = Option(cwd), runJVMOptions = jvm_, connectInput = connectInput, envVars = envVars) val process = new Fork("java", Option(mainClass)).fork(options, args) def cancel() = { println("Run canceled.") process.destroy() 1 } val errno = try process.exitValue() catch { case e: InterruptedException => cancel() } if(errno==0) { if (args.contains("-v")) cwd.list.foreach(f => println(f)) cwd.listFiles } else { throw new IllegalStateException(s"errno = ${errno}") } }

Then you need to wire DataNucleus Enhancer as part of your build process. This is done via manipulateBytecode sub-task, as demonstrated below:

lazy val model = project.in(file("model")) // .settings(publishSettings:_*) .settings(librarySettings:_*) .settings(paranoidOptions:_*) .settings(otestFramework: _*) .settings(deps_tagging:_*) //-- .settings(deps_stream:_*) .settings(deps_database:_*) .settings( Seq( // This trick requires SBT 0.13.8 manipulateBytecode in Compile := { val previous = (manipulateBytecode in Compile).value sbtRunner( // javaRunner also works! mainClass = "javax.jdo.Enhancer", args = Seq( "-v", "-pu", "persistence-h2", "-d", (classDirectory in Compile).value.absolutePath), classpath = (managedClasspath in Compile).value.files ++ (unmanagedResourceDirectories in Compile).value :+ (classDirectory in Compile).value, cwd = (classDirectory in Compile).value, javaHome = javaHome.value, envVars = (envVars in Compile).value ) previous } ):_*) .dependsOn(util)

For a complete example, including a few JDO annotated persistence classes and some rudimentary test cases, please have a look at

http://github.com/frgomes/poc-scala-datanucleus

Answer2:

I think the issue is you're passing your dependency jars as boot jars not as the classpath.

From your poc project perhaps something like:

val jvm_ = runJVMOptions.map(p => p.toString) ++ Seq("-cp", cp_ mkString java.io.File.pathSeparator)

Recommend

  • How to set an entity field that does not exist on the table but does exists in the raw SQL as an ali
  • Meteor.. accounts- password— Create account on client without login
  • Thread synchronization with syncwarp
  • Angular2 - Template reference inside NgSwitch
  • Jquery Knockout: ko.computed() vs classic function?
  • How can I replace the server in Web Component Tester
  • How to define and use opencv mat of user type
  • Alert pop up with LWUIT
  • Disabling Alt-F4 on a Win Forms NotifyIcon
  • FB SDK and cURL: Unknown SSL protocol error in connection to graph.facebook.com:443
  • Using $this when not in object context
  • Dynamically accessing properties of knockoutjs observable array
  • Jenkins: How To Build multiple projects from a TFS repository?
  • Azure Cloud Service Web Role web pages do not load
  • Fetching methods from BroadcastReceiver to update UI
  • How do I fake an specific browser client when using Java's Net library?
  • How reduce the height of an mschart by breaking up the y-axis
  • Sony Xperia Z Tablet not found by adb
  • How to recover from a Spring Social ExpiredAuthorizationException
  • How to set/get protobuf's extension field in Go?
  • DirectX11 ClearRenderTargetViewback with transparent buffer?
  • Does CUDA 5 support STL or THRUST inside the device code?
  • ILMerge & Keep Assembly Name
  • Cassandra Data Model
  • Perl system calls when running as another user using sudo
  • Trying to switch camera back to front but getting exception
  • ActionScript 2 vs ActionScript 3 performance
  • Importing jscolor library in angular 2
  • Why is the timeout on a windows udp receive socket always 500ms longer than set by SO_RCVTIMEO?
  • Large data - storage and query
  • Web-crawler for facebook in python
  • How can I estimate amount of memory left with calling System.gc()?
  • Apache 2.4 - remove | delete | uninstall
  • WOWZA + RTMP + HTML5 Playback?
  • Run Powershell script from inside other Powershell script with dynamic redirection to file
  • Unit Testing MVC Web Application in Visual Studio and Problem with QTAgent
  • Proper folder structure for lots of source files
  • AngularJs get employee from factory
  • Hits per day in Google Big Query
  • How to CLICK on IE download dialog box i.e.(Open, Save, Save As…)