Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Embedding a Java runtime into a sandboxed Cocoa Mac app

Does anybody know how to embed a Java runtime into a Mac Cocoa (sandboxed) app so that the app can run a .jar file without the user having to install Java separately?

We have a Cocoa application that we sell through the Mac App Store as well as on our own site which can use Java for a specific task. It's a writing app, and because the standard NSText exporters for .doc, .docx and .odt are very limited (not supporting headers, footers, images etc), we write out to RTF and then use Java-based converters (from Aspose.com) to convert the RTF to .doc, .docx or .odt, depending on the chosen export format.

I do this by by using NSTask to call on /usr/bin/java and run a bundled .jar file that runs the conversion routines. This all works beautifully--as long as Java is installed on the user's Mac. If it's not installed, our app asks the user if he or she would like to install Java to benefit from the enhanced converters, or instead fall back on the NSText lossy converters. If the user chooses to install Java, we call java_home --request to invoke OS X's Java installer.

However, now that Apple has ended (or is ending) direct support for Java, this approach is problematic. During our last Mac App Store review, we were told that we would soon need to stop asking the user if he or she wanted to install Java. I have read that Apple's recommended route going forward is to embed a Java Runtime Environment into Cocoa applications that need to run Java. (E.g: http://lists.apple.com/archives/java-dev/2011/Jul/msg00038.html )

Over the past few days, I've therefore been researching how I can go about embedding a JRE in my app, but I haven't found any instructions on how to do so anywhere - nothing that I understand, at least. Someone asked a similar question here a couple of years ago:

Bundling a private JRE on Mac OS X

However, the answers there, and on other sites I've found, are all about bundling a Java application as a Mac application, not about including a JRE with an existing Cocoa application. (Although I accept that my lack of experience with Java may mean that I just don't understand how to convert the instructions for my purposes - take me out of Xcode and I'm out of my comfort zone.) Likewise: https://wikis.oracle.com/display/OpenJDK/How+to+embed+a+.jre+bundle+in+your+Mac+app

It seems that a couple of applications have done this (e.g. Cyberduck), but no one has documented the process yet. From what I've read, I think I need to either grab the Java Virtual Machine from Oracle's JDK or OpenJDK and include that in my Cocoa app and call on java from within that, but if I did so, the Java executables within the copy of the JVM wouldn't be sandboxed, so my app wouldn't get through the MAS review process. So presumably I need to somehow build a copy of the JDK and sandbox it, most likely as part of my larger Xcode project? I'm not even sure where to start; I'm guessing it's still early days and that not many Mac developers have had to do this yet.

Has anyone got any experience of embedding a JRE into a Cocoa application in such a way that the app can just call on it using NSTask? And in such a way that the JRE is fully sandboxed and thus acceptable by the Mac App Store? Or, has anyone successfully created a sandboxed Cocoa app that runs a .jar file without needing a separate Java installation? If so, would you be willing to share how you did it?

(I took out a developer tech support incident with Apple asking for help on this, but was told that they couldn't help at all given that Java is now considered a third-party development environment.)

Many thanks in advance for any suggestions or pointers to web pages I may have missed.

like image 494
kayembi Avatar asked Jun 06 '13 11:06

kayembi


2 Answers

I can't be sure, but I strongly suspect that when that Apple engineer says they suggest "embedding" the Java runtime, he doesn't mean "bundle a separate executable into your app bundle and run it in a separate process" so much as he means "spin up a Java VM within your own process and run what you need to in that VM." It's arguably more cumbersome to do this than it is to run Java in a separate process, but it is possible.

Have a look at the NSJavaVirtualMachine class. It may not help you if the user doesn't have Java installed, but the idea would be the same - you would just build yourself a library containing the Java Runtime, link it into your main binary, then create and manage the JVM using the JNI API. Google for JNI_CreateJavaVM.

I gave a quick example of spooling up a JVM and calling into Java using JNI in another question (Calling a Java class function from Cocoa with JNI), although that was predicated on using the Apple-bundled JavaVM.framework, so it doesn't include steps on building the library you would need to link. I don't have any experience with that, unfortunately.

like image 85
ipmcc Avatar answered Nov 05 '22 01:11

ipmcc


To answer your sub-question about sandbox and aux binaries, that's not a problem. You just have to mark the binary as inheriting the sandbox.

Have an .entitlements file like this:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>com.apple.security.app-sandbox</key>
    <true/>
    <key>com.apple.security.inherit</key>
    <true/>
</dict>
</plist>

And make sure you have a build phase in Xcode that codesigns the aux binary:

codesign --entitlements myjava.entitlements -s "${CODE_SIGN_IDENTITY}" java
like image 41
Václav Slavík Avatar answered Nov 05 '22 01:11

Václav Slavík