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

vertexbuffer
Hello, can anybody help here..? I have a list of players and I what to delete an element, but every for loop the list is reverting to ori...
New
openscript
Hello! Sorry for this astonishing simple question, but I’m really stuck. I try to set up the intellij-elixir plugin, but I don’t know ho...
New
nsuchy
Hi. I’ve noticed that Windows Powershell has it’s own IEX command and you cannot access Elixir’s IEX due to the conflict. This isn’t a cr...
New
jay1
Why is it that the mnesia database isn’t the most preferred database for use in Elixir/Phoenix?
New
alice
Hey, Just curious what are the main benefits of Elixir compared to Clojure? When is Elixir more useful than Clojure and vice versa? Th...
New
WestKeys
Currently suffering from paralysis by [HTTP client] analysis. This is rather unusual in Elixirland as there tends to be consensus on the ...
New
JorisKok
I have a server on AWS, and was running a load test using artillery. When looking at the Phoenix dashboard I see the Ports going to 100% ...
New

Other popular topics Top

KronicDeth
Elixir plugin for JetBrain’s IntelliJ Platform (including Rubymine) This is a plugin that adds support for Elixir to JetBrains IntelliJ...
289 36654 110
New
joaquinalcerro
Hi there, I am working with Ecto-Postgresql and I need to call all of the records from a specific table but the table has 40,000 records...
New
stefanchrobot
What’s the safe way to decode a JSON string into a struct? I want to avoid calling String.to_atom. Jason.decode can give me a map with st...
New
dokuzbir
I want to highlight html closing tags when i click a html tag. That works in .html files but doesnt work for html.eex templates. How can...
New
shijith.k
I am trying to start a new phoenix project with elixir 1.9, but mix phx.new does not work. It says that ** (Mix) The task "phx.new" could...
New
siddhant3030
Hi, I have to write a raw query for one of my project. But till now I have used ecto queries and don’t have much experience writing raw ...
New

We're in Beta

About us Mission Statement