McTheels

McTheels

Call Java from Elixir

Good day,

I am trying to make use of a Java class that handles encryption and decryption. How can I call the member function of that class from a .java file in Elixir?

Thanks!

Most Liked

mindok

mindok

Ports are the way to go - search for Erlang Java ports (either here or google). When I’m back at the PC I’ll dig something up if you don’t manage to get anywhere.

But yes, if it’s standard encryption it would be less hassle and likely more reliable in deployment and operation to rewrite in Elixir.

mindok

mindok

Ok,

Here’s an ugly PoC chunk of java code. I have extracted out the bits that are sensitive so it almost certainly won’t work as-is, but hopefully it gives you some pointers. You will need to install the Ericsson otp java package (see the import statement at the top).

There were a couple of gotchas:

  1. Marshalling data types - strings, doubles, dates etc all need to be explicitly marshalled between built-in Java datatypes and otp ready data types. I wrote quite a few helper functions to do this.
  2. The Ericsson package fails to emit a required byte to the stream of bytes sent back to the port, so the sendResponse method has the required hack to make it work.
  3. Note the warnings from @jayjun in Killing java processes started from Port.open - #10 by jayjun re: port buffer deadlocks. I haven’t handled that potential issue as it was good enough for our PoC as-is

Don’t pick on my java code - I hadn’t written any since 2005 before this.

package com.mindok;

import java.io.*;
import java.nio.ByteBuffer;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;


import com.ericsson.otp.erlang.*;

public class Main {

    static SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd' 'HH:mm");

    public static void main(String[] args) throws Exception {
        OtpErlangObject payload = null;

        while(true) {
            try {
                payload = receivePayload();
            } catch (Exception e) {
                sendResponse(toOtpString("Receive " + e.getStackTrace()));
                break;
            }

            if (payload == null) {
                sendResponse(toOtpString("No payload received - exiting"));
                break;
            }

            OtpErlangObject response = null;
            try {
                response = processPayload(payload);
            } catch (Exception e) {
                sendResponse(toOtpString("Process " + e.getMessage()));
                Object[] ste = e.getStackTrace();
                for (int i=0; i < 5; ++i) {
                    sendResponse(toOtpString("Process " + ste[i]));
                }
                break;
            }
            if (response == null) {
                sendResponse(toOtpString("No response received - exiting"));
                break;
            }

            try {
                sendResponse(response);
            } catch (Exception e) {
                sendResponse(toOtpString("Send " + e.getStackTrace()));
                break;
            }
        }

    }

    private static void sendResponse(OtpErlangObject response) throws IOException {
        OtpOutputStream otpOutputStream = new OtpOutputStream(response);
        byte[] output = otpOutputStream.toByteArray();

        System.out.write(encodeLength(output.length + 1)); // Add one for the header byte added below
        System.out.write((byte) 131); // for some reason the encoding doesn't include the very thing that identifies it as Erlang ETF
        System.out.write(output);
    }

    private static byte[] encodeLength(int length) {
        ByteBuffer bb = ByteBuffer.allocate(4);
        bb.putInt(length);
        return bb.array();
    }

    private static OtpErlangObject processPayload(OtpErlangObject payload) {
        OtpErlangTuple tuple = (OtpErlangTuple)payload;
        OtpErlangAtom command = (OtpErlangAtom)tuple.elementAt(0);
        OtpErlangObject commandParameters = tuple.elementAt(1);

        switch (command.atomValue()) {
            case "open_file" :
                return packageOutput("open_file", openFile(commandParameters));

            case "something_else" :
                // *** etc 

            default:
                return payload;
        }
    }

    private static OtpErlangObject openFile(OtpErlangObject commandParameters) {
        String outcome = "ok";
        String msg = "Successfully read ";
        String fileName = "";
        try {
            fileName = new String(((OtpErlangBinary)commandParameters).binaryValue());
            msg = "Successfully read " + fileName;
        } catch (Exception e) {
            outcome = "error";
            msg = "Error reading file: " + e.getMessage();
        }

        if (outcome != "error") {
            try {
                // ***  Do something with file that was opened
            } catch (Exception e) {
                outcome = "error";
                msg = "Error reading file " + fileName + " - " + e.getMessage();
            }
        }

        OtpErlangObject[] tupleContents = {toOtpAtom(outcome), toOtp(msg)};
        return new OtpErlangTuple(tupleContents);
    }

    private static OtpErlangObject packageOutput(String cmd, OtpErlangObject payload) {
        OtpErlangObject cmdAtom = toOtpAtom(cmd);
        OtpErlangObject[] tupleContents = {cmdAtom, payload};
        return new OtpErlangTuple(tupleContents);
    }

    private static int fromOtpInt(OtpErlangObject otpInt) {
        try {
            return ((OtpErlangLong)otpInt).intValue();
        } catch (OtpErlangRangeException e) {
            return -1;

        }
    }

    private static OtpErlangObject toOtp(Object o) {
        if (o == null) {
            return new OtpErlangAtom("nil");
        } else {
            if (o instanceof Integer) { return toOtpInt((Integer)o);}
            if (o instanceof String) { return toOtpString((String)o);}
            if (o instanceof Date) { return toOtpDate((Date)o);}
            if (o instanceof Double) { return toOtpDouble((Double)o);}
        }
        return new OtpErlangAtom("nil");
    }

    private static OtpErlangObject toOtpDouble(Double dbl) {
        return new OtpErlangDouble(dbl);
    }

    private static OtpErlangString toOtpString(String str) {
        return new OtpErlangString(str);
    }

    private static OtpErlangString toOtpDate(Date date) {
        return toOtpString(dateFormatter.format(date));
    }

    private static OtpErlangLong toOtpInt(Integer integer) {
        return new OtpErlangLong(integer);
    }

    private static OtpErlangAtom toOtpAtom(String str) {
       return new OtpErlangAtom(str);
    }


    private static OtpErlangObject receivePayload() throws Exception {
        byte[] msgLen = new byte[4];

        int readByteCount = System.in.read(msgLen);
        if (readByteCount != msgLen.length) {throw new Exception("Didn't get message length");}

        ByteBuffer bb = ByteBuffer.wrap(msgLen);
        int encodedLength = bb.getInt();

        byte[] rawPayload = new byte[encodedLength];

        readByteCount = System.in.read(rawPayload);
        if (readByteCount != encodedLength) {throw new Exception("Payload didn't match message length");}

        OtpInputStream otpInputStream = new OtpInputStream(rawPayload);
        return otpInputStream.read_any();
    }

}

Last Post!

mindok

mindok

One more thing. To open the port on the elixir side you will need something like:

    jar_location = ~S|path\to\my\java_jars\encryptor_interface.jar|
    cmd = "java -jar #{jar_location}"
    Port.open({:spawn, cmd}, [:binary, :exit_status, :use_stdio, :stderr_to_stdout, {:packet, 4}])

Where Next?

Popular in Questions Top

JeremM34
Hello, how can I check the Phoenix version ? Thanks !
New
Brian
What is the proper way to load a module from a file in to IEX? In the python world, doing something like this pretty standard: from ....
New
joeerl
Hello again - after a longish gap I’ve decided I really must dig into Elixir and see what’s been happening here - so I have a few questio...
New
jerry
Good day to you all. I have been struggling to get a query involving like and ilike to work. Can anyone assist me on this, please? pro...
New
stefanluptak
Hello everybody, usually, I use a 29" ultra-wide monitor for VSCode which can easily accomodate explorer (files panel) + file with code ...
New
freewebwithme
Using vs code and installed ElixirLS: support and debugger. And I got an error popped up on start up says Failed to run ‘elixir’ comma...
New
jason.o
In the code below, if the create action is not set to accept “extra_key” as an input, it errors out with a message shown above. Is there ...
New

Other popular topics Top

minhajuddin
I have seen a lot of code which picks the first element from a list using Enum.at(0) instead of List.first. Is there a reason why people ...
New
vonH
When I run the Plug and I recompile I wind up having to use Ctrl C to quit iex and start again. Witht the help of rlwrap I can use the cu...
New
vonH
In asking this question I am more interested about the expressiveness of the language itself and less concerned about the availability of...
New
dogweather
I wrote this comment on r/haskell, and it’s not popular there. :wink: But I think I’m on to something… Haskell reminds me of Java, and e...
New
sorentwo
Hello! tl;dr Announcing Oban, an Ecto based job processing library with a focus on reliability and historical observability. After spen...
985 44608 311
New
saif
Hello everyone, Long time lurker first time poster here. I’ve recently begun working on Elixir full-time again! :raised_hands: It’s been...
New

We're in Beta

About us Mission Statement