Running a .jar file from inside a Phoenix/Elixir app

We’ve built a middleware solution for the non-profit world (mostly public-health data integration projects) on Phoenix/Elixir that executes “jobs” for our users based on specific events. As it currently stands, these jobs are executed in a Node.js VM—the essential job running code in Elixir is a System.cmd/3 call which executes a Node.js program with some arguments. It looks like this:

def perform(%Job{success: nil} = job) do

    arguments = [
      "core",
      "execute",
      "-e", job.expression_path,
      "-l", "#{Application.get_env(:the_app, :language_packs_path)}/" <> job.language <> ".Adaptor",
      "-s", job.state_path |
      (if job.final_state_path, do: ["-o", job.final_state_path], else: [])
    ]

    env = [
      {"NODE_PATH", "./node_modules"},
      {"NODE_ENV", to_string(Mix.env)},
      {"PATH", "./node_modules/.bin:" <> System.get_env("PATH")}
    ]

    Logger.debug [
      "Executing with:\n",
      "Environment: \n",
      Enum.map(env, fn {k,v} -> "  #{k}: #{v}\n" end),
      "Command: \n  ",
      Enum.join(arguments, " ")
    ]

    result = System.cmd("env", arguments, [ env: env, stderr_to_stdout: true ])

    case result do
      {stdout, 0} ->
        %{ job | log: String.split(stdout, ~r{\n}), success: true, exit_code: 0 }
      {stdout, exit_code} ->
        %{ job | log: String.split(stdout, ~r{\n}), success: false, exit_code: exit_code }
    end
  end

It works very well, but I’m now trying to extend the functionality of our app so that we can run either that Node.js program (essentially System.cmd("node program.js execute -l ./lib/Adaptor -s ../tmp/state.json -o ../tmp/output.json -e ../tmp/expression.js")) or run a Selenium & Sikuli (OpenCV-based) webdriver and image recognition tool for automating the use of webapps that lack an accessible API, don’t have accessible HTML elements since they live inside canvases, and are otherwise impossible to automate.

Using essentially the same structure, I’d like to run that .jar with java rather than the .js file with Node. It fails with the following error:

iex(1)> System.cmd("/lib/runsikulix -r /lib/SAP_VMware.sikuli --args /lib/SAP_VMware.sikuli/tmp/state2.json", [])
** (ErlangError) Erlang error: :enoent
    :erlang.open_port({:spawn_executable, '/lib/runsikulix -r /lib/SAP_VMware.sikuli --args /lib/SAP_VMware.sikuli/tmp/state2.json'}, [:use_stdio, :exit_status, :binary, :hide, {:args, []}])
    (elixir) lib/system.ex:611: System.cmd/3

The desired outcome is that the .jar runs in a new process and reports back on its sucess or failure once it’s done, the same way our current System.cmd(node app.js) setup works. Does anyone know how to acheive this? Or:

  1. Should an Elixir app be able to spawn and run a java executable program, in theory? Is this a job that should be handled by jInterface?
    (http://erlang.org/doc/apps/jinterface/jinterface_users_guide.html)
  2. What if that java program, in turn, tried to launch a headless browser on the server?
  3. :enoent usually means Erlang can’t find the file. I’ve tried playing around with the paths, and all but my current setup yield a simple :enoent. The current setup yields this :spawn_executable error and makes me think it is actually trying to run the jar file. What does :spawn_executable mean?

Thanks in advance for considering this.

Java problems often relate back to setting the correct classpath - so you may want to try explicitly including it in the arguments. For example:

      %% In the same way, finding an otp lib dir is also tricky, hence otp_lib
      %% We build the classpath with our priv dir (where we put all the jars we need)
      %% and OtpErlang.jar from jinterface
      Classpath = otp_lib("/OtpErlang.jar") ++ [$: | Priv ++ "/*"],
      Port =
        erlang:open_port({spawn_executable, Java},
                         [{line,1000}, stderr_to_stdout,
                          {args, ["-classpath", Classpath, %% The classpath
                                 %% The class with the main method on our Java code
                                  "net.inaka.fetjaba.Node",
                                  %% The command line arguments
                                  %% including the node names and
                                  %% the cookie (you'll see how I use them on java side)
                                  ThisNode, JavaNode,
                                  erlang:get_cookie()]}]),
  

from From Erlang to Java and Back Again: Part 1

For a tutorial on how to control ports directly see The Erlangelist: Outside Elixir: running external programs with ports


runsikulix boils down to this line:

"$JAVABIN" $PROPS -jar "$shome/$sjar.jar" $SIKULI_COMMAND

Once you resolve the content of those environment variables you can construct a command that feeds additional arguments into the Java runtime.

Yes Elixir can spawn it fine, same way with System.cmd as always (I have no clue what that /lib/runsikulix bit is, I’d use java -jar <classpathsArgument> <otherArguments> blah.jar). No don’t use jinterface for it as it is useless on the java side as java would have to execute it in a new process as well.

OK, that was quite simple… thank you both for the swift responses. I feel like this was a function of me being a complete newb with Java. I tested it out with a very simple jar file and it worked perfectly.

iex(5)> System.cmd("java", ["-jar", "Main.jar"]) 
{"Hello world, I'm running.\n", 0}

@OvermindDL1, just for the record, the runsikuli bit you saw (http://sikulix.com/) is an automation tool that’s quite useful when you need to automate tasks but are not able to access HTML elements or you’re using a desktop application with no API—it uses OpenCV to recognize UI elements visually, then executes clicks and text entry. Very much a “last resort” but seems super useful for automated testing or task automation projects.

1 Like

As a side note, if you’re managing external processes from a BEAM instance, I have found exexec to be a very useful tool. The documentation is a little lacking, so you may have to go down and read the erlexec docs (which exexec wraps). It lets you manage several processes at once, kill them at will, be notified if they die etc.

3 Likes