Integração com Java

O Engine permite a integração com Java por meio da API Enginelet. Essa API permite a execução síncrona de códigos Java a partir do JavaScript e vice-versa. Os principais componentes dessa API são:

  • Classe JavaScript Enginelet: permite a execução de classes Java que implementem a interface “com.nginstack.engine.Enginelet”.
  • Interface Java com.nginstack.engine.Enginelet: define o método handleCommand que deverá ser implementado pelas classes Java que serão executadas a partir do ambiente JavaScript.
  • Classe Java com.nginstack.engine.EngineJavaInterface: define métodos estáticos que permitem que o código Java execute um script JavaScript no Engine.

As classes Enginelet e EngineJavaInterface criam uma interface padronizada de comunicação entre os dois ambientes utilizando parâmetros e retornos do tipo String. Parâmetros de outros tipos devem ser convertidos em strings, sendo recomendado o uso do JSON para tipos mais complexos.

Os erros lançados durante a execução do script JavaScript indicado no método runScript da classe EngineJavaInterface são capturados pelo Engine e relançados dentro do ambiente Java. O mesmo ocorre com os erros Java lançados durante a execução do método runCommand da classe Enginelet. Esses erros devem ser esperados pelo desenvolvedor e devem ser tratados adequadamente.

Atualmente não é possível depurar a execução de códigos Java executados a partir do Engine. Para auxiliar na investigação dos erros, a saída padrão e a de erro do Java são interceptadas pelo Engine e redirecionadas para o arquivo “java.log”, permitindo que elas possam ser utilizadas para coletar informações relevantes durante a execução dos códigos Java.

Importante: o Java é integrado ao Engine via biblioteca nativa da JVM. Essa integração cria um acoplamento forte entre o Engine e o Java em um mesmo processo do sistema operacional, tornando impossível isolar a alocação de recursos e falhas de cada ambiente. A memória alocada pelo ambiente Java se mistura com a memória alocada pelo Engine e eventuais falhas de códigos Java podem comprometer a estabilidade e segurança do Engine, podendo inclusive provocar a queda do serviço. Por esse motivo, é recomendado que bibliotecas Java de alta complexidade, que aloquem muitos recursos ou que sejam implementadas por terceiros não sejam integradas via Enginelet. Para esses casos, pode-se utilizar a classe OSApplication para disparar uma aplicação Java em um processo separado e se integrar com ela por meio de uma API HTTP, Web Sockets ou arquivos.

Pré-requisitos

A API Enginelet é compatível com o Java 8 ou superior. O Engine detecta automaticamente o runtime Java a partir das configurações do registro do Windows ou do PATH. O caminho de uma instalação específica do Java pode ser informado ao Engine caso seja necessário, conforme orientação do manual Instalação do sistema.

Configuração

As classes e interfaces Java de integração do sistema são distribuídas no arquivo “enginelet.jar”. Esse arquivo é armazenado no diretório “/products/Engine/library/javalibs/” da Virtual File System. Para permitir a compilação das classes Java integradas ao sistema, é necessário fazer o download desse arquivo e adicioná-lo ao CLASSPATH do Java utilizado no ambiente de desenvolvimento. Para simplificar esse processo, esse pacote também é distribuído em um repositório Maven que pode ser configurado em uma ferramenta de build automatizada.

O pacote “enginelet” não é distribuído no Maven Central Repository, portanto, além da dependência em si, o repositório da plataforma também deve ser configurado na ferramenta de build utilizada. Essa configuração precisa ser realizada uma única vez, adicionando as configurações abaixo nos locais relevantes:

Gradle (build.gradle):

plugins {
  id 'com.google.cloud.artifactregistry.gradle-plugin' version '2.2.1'
}

repositories {
  maven {
    url "artifactregistry://us-east1-maven.pkg.dev/nginstack/java"
  }
}

Maven (pom.xml):

<project>  
  <!-- Other configs -->  
  <repositories>
    <!-- Other repositories -->
    <repository>
      <id>artifact-registry</id>
      <url>artifactregistry://us-east1-maven.pkg.dev/nginstack/java</url>
      <releases>
        <enabled>true</enabled>
      </releases>
      <snapshots>
        <enabled>true</enabled>
      </snapshots>
    </repository>
  </repositories>

  <build>
    <!-- Other build configs -->
    <extensions>
      <extension>
        <groupId>com.google.cloud.artifactregistry</groupId>
        <artifactId>artifactregistry-maven-wagon</artifactId>
        <version>2.2.0</version>
      </extension>
    </extensions>
  </build>
</project>

Uma vez configurado o repositório da plataforma, deve ser adicionada a dependência “enginelet”. A versão desse pacote segue uma numeração diferente do restante do sistema. Para determinar a versão adequada, deve ser observado o histórico de alterações no final deste manual e ser escolhida uma versão compatível com a versão do sistema na qual a classe Java será utilizada. É importante observar que, independente da versão escolhida, a versão do “enginelet” que de fato será utilizada é aquela do pacote JAR gravado na Virtual File System, normalmente a última versão disponível.

Seguem abaixo as configurações que devem ser realizadas no Gradle ou Maven para utilizar a última versão do pacote:

Gradle (build.gradle):

implementation group: 'com.nginstack', name: 'enginelet', version: '1.0'

Maven (pom.xml):

<dependency>
  <groupId>com.nginstack</groupId>
  <artifactId>enginelet</artifactId>
  <version>1.0</version>
</dependency>

Caso o VSCode seja utilizado no desenvolvimento, é recomendado que as duas configurações abaixo sejam adicionadas no arquivos .vscode/settings.json, permitindo que os fontes e o JavaDoc do Enginelet sejam descarregados na instalação da dependência:

"java.eclipse.downloadSources": true,
"java.maven.downloadSources": true,

Distribuição

Os códigos Java relacionados às integrações com o sistema devem ser compilados e distribuídos por meio de pacotes JAR. Esses pacotes JAR devem ser gravados na Virtual File System, em um diretório com o nome “javalibs”.

Por convenção, cada produto do sistema tem o seu próprio diretório “javalibs”, normalmente no caminho “/products/<product-name>/library/javalibs”. É recomendado que pacotes relacionados às customizações sejam gravados no caminho “/products/custom/library/javalibs”.

No primeiro uso da API Enginelet, o Engine grava todos os arquivos dos diretórios “javalibs” da Virtual File System no diretório local do sistema operacional “<engine.dataDir>/javalibs”. Em seguida ele adiciona todos os arquivos desse diretório no CLASSPATH do ambiente Java. Esse sincronismo é realizado uma única vez antes da inicialização da Java VM, portanto as alterações nos arquivos dos diretórios “javalibs” exigem o reinício do Engine para serem efetivadas.

O sincronismo automático das dependências Java pode ser desativado caso seja necessário testar uma modificação sem alterar os arquivos da Virtual File System. Para isso deve ser criado o arquivo “<engine.dataDir>/javalibs/.ignoresync” e os JAR atualizados devem ser gravados diretamente no diretório “javalibs” do sistema operacional.

Exemplos de uso

Implementação de uma classe Java Enginelet

package com.example;

import com.nginstack.engine.Enginelet;
import com.nginstack.engine.UnsupportedCommandException;

public class EchoEnginelet implements Enginelet {
  public String handleCommand(String commandName, String[] commandArgs) {
    if (commandName.equals("echo")) {
      return String.join(",", commandArgs);
    } else {
      throw new UnsupportedCommandException(commandName);
    }
  }
}

Utilização de uma classe Java Enginelet no JavaScript

const Enginelet = require('@nginstack/engine/lib/java/Enginelet.js');

const enginelet = new Enginelet('com.example.EchoEnginelet');
enginelet.handleCommand('echo', 'test'); // => 'test'

Execução de um script JavaScript a partir do Java

package com.example;

import java.util.HashMap;

import com.nginstack.engine.EngineJavaInterface;
import com.nginstack.engine.Enginelet;

public class RunScriptEnginelet implements Enginelet {

  private static long scriptKey = 123456789;

  public String handleCommand(String commandName, String[] commandArgs) {
    HashMap<String, String> parameters = new HashMap<>();
    parameters.put("value", "test");
    if (commandName.equals("test")) {
      return EngineJavaInterface.runScript(scriptKey, parameters);
    } else {
      throw new UnsupportedCommandException(commandName);
    }
  }
}

Leitura dos parâmetros enviados a partir do Java

const result = 'Value: ' + javaRequest.getParameter('value');
result; // => 'Value: test'

Histórico de alterações

  • Versão 1.0: versão inicial do pacote no repositório Maven. Compatível com o Engine 71 ou superior.