sexta-feira, 21 de fevereiro de 2014

Projeto EAR com DLLs

Pelos últimos artigos deste blog, percebe-se que trabalho com C++ e Java e não dispenso a utilização do Maven nos meus projetos. O nar-maven-plugin permite que eu desenvolva alguma coisa em C++ usando o Maven e, o melhor, ele permite Java e C++ no mesmo projeto, mantendo casadas as interfaces JNI.

Recentemente tive que utilizar algumas bibliotecas Java e C++ num projeto web com Enterprise Java Beans, empacotado como EAR (Enterprise Archive). O Maven tem um plugin específico para empacotar este tipo de projeto, chamado de maven-ear-plugin, mas com limitações com relação aos tipos de artefatos aceitos para empacotamento e as DLLs não são aceitas neste caso.

O Problema

Para entender melhor, a figura abaixo exemplifica, de forma simplificada, o problema no meu projeto. O módulo NAR produz um artefato de projeto do tipo NAR, pelo nar-maven-plugin. Este artefato de projeto contém um arquivo JAR e uma DLL compactados. Este módulo é um projeto com interface JNI entre o Java e o C++.  A dependência entre o módulo EJB e NAR é resolvida facilmente pelo próprio nar-maven-plugin. O problema surge no módulo EAR, quando o pluging do Maven, que faz o empacotamento, não consegue tratar artefatos do tipo NAR ou mesmo do tipo DLL.


Mesmo que o plugin conseguisse empacotar o artefato NAR no módulo EAR, o problema continuaria porque os arquivos JAR e DLL contidos no artefato NAR precisariam ser extraídos.

A Solução 

A solução para este problema consiste em i) modificar a dependência entre os módulos EJB e NAR, ii) empacotar a DLL no arquivo EJB, iii) incorporar os byte codes (arquivos class) do arquivo JAR (do módulo NAR) no arquivo EJB e iv) desempacotar a DLL em tempo de execução.

Passo 1

A dependência do projeto NAR pelo projeto EJB precisa ser modificada para que o projeto EAR a ignore. Em outras palavras, ao empacotar o projeto EAR, o Maven, quando desenrola a cadeia de dependências, precisa ignorar a dependência do projeto EJB pelo projeto NAR.

Para tanto, no projeto EJB (arquivo pom.xml) é preciso definir a tag OPTIONAL como TRUE para descrição de dependência do projeto NAR., conforme mostrado abaixo:

<dependency>
    <groupid>blog.dacanal</groupid>
    <artifactid>jni</artifactid>
    <version>1.0-SNAPSHOT</version>
    <type>nar</type>
    <optional>true</optional>
</dependency>

O mesmo seria necessário caso a dependência fosse um módulo do tipo DLL.

Passo 2

Ao gerar o módulo EJB, a DLL precisa ser empacotada no arquivo EJB como um recurso. O maven-ant-plugin pode ser utilizado para copiar, no instante correto, a DLL para a pasta onde os byte codes foram gerados. A configuração do plugin é apresentada a seguir:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-antrun-plugin</artifactId>
    <version>1.7</version>
    <executions>
        <execution>
            <id>copy-resources</id>
            <phase>process-resources</phase>
            <goals>
                <goal>run</goal>
            </goals>
            <configuration>
                <target>
                    <copy file="${basedir}\target\nar\jni-1.0-SNAPSHOT-x86-Windows-gpp-jni\lib\x86-Windows-gpp\jni\jni-1.0-SNAPSHOT.dll" todir="${basedir}\target\classes\lib" />
                </target>                                        
            </configuration>            
        </execution>
    </executions>
</plugin>

Para que o maven-ant-plugin faça seu trabalho corretamente, antes o nar-maven-plugin precisa ser configurado no projeto EJB para que ele descompacte a DLL, do arquivo NAR, no instante adequado. Lembre-se de que o artefato NAR está no repositório Maven (local ou remoto).

<plugin>
    <groupId>com.github.maven-nar</groupId>
    <artifactId>nar-maven-plugin</artifactId>    
    <version>3.0.1-SNAPSHOT</version>  
    <extensions>true</extensions>  
    <executions>
        <execution>
            <id>unpack-nar</id>
            <goals>
                <goal>nar-unpack</goal>
            </goals>
            <phase>process-sources</phase>
            <configuration>                                                                                                                     
            </configuration> 
        </execution>
    </executions>                                          
</plugin>

Passo 3

Falta ainda incorporar os byte codes do projeto NAR no projeto EJB. Agora, o maven-dependecy-plugin faz este trabalho, conforme configurado abaixo:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-dependency-plugin</artifactId>
    <version>2.8</version>
    <executions>
        <execution>
            <id>unpack-jar</id>
            <phase>prepare-package</phase>
            <goals>
                <goal>unpack</goal>
            </goals>
            <configuration>
                <artifactItems>
                    <artifactItem>
                        <groupId>blog.dacanal</groupId>
                        <artifactId>jni</artifactId>
                        <version>1.0-SNAPSHOT</version>
                        <type>nar</type>
                        <overWrite>true</overWrite>
                        <outputDirectory>${project.build.directory}\classes</outputDirectory>
                        <includes>**/*.class</includes>
                        <excludes>**/*test.class</excludes>
                    </artifactItem>
                </artifactItems>                           
            </configuration>
        </execution>
    </executions>
</plugin> 

As configurações Maven apresentadas nos passos 2 e 3, todas realizadas no módulo EJB, são importantes mas o trabalho ainda não terminou.

Passo 3

A DLL, agora encapsulada no módulo EJB, precisa ser desencapsulada no instante em que for utilizada. Esta operação é realizada por código Java, em tempo de execução. No caso exemplificado, o código deve ser incorporado ao módulo EJB. Veja abaixo a codificação:

public static void extractNativeLibraries() throws IOException {
    // Read the jar       
    CodeSource src = DataLoaderProxy.class.getProtectionDomain().getCodeSource();
    if (src != null && src.getLocation().getFile().endsWith(".jar")) {
        URL jar = src.getLocation();
        String filePath = jar.getPath();
        String outputPath = "c:\\temp"; // not a good idea!
        JarFile jarFile = new JarFile(new File(filePath));
        unpackDLLs(jarFile, outputPath);
    } 
}

private static void unpackDLLs(JarFile jarFile, String destDir) throws FileNotFoundException, IOException {
    for (Enumeration entries = jarFile.entries(); entries.hasMoreElements();) {
        JarEntry entry = entries.nextElement();
        if (entry.getName().endsWith(".dll")) {
            int i = entry.getName().lastIndexOf('/');
            File outputFile = new File(destDir + "\\" + entry.getName().substring(i > 0 ? i + 1 : 0));
            outputFile.getParentFile().mkdirs();
            try (FileOutputStream out = new FileOutputStream(outputFile); InputStream in = jarFile.getInputStream(entry);) {
                byte[] buffer = new byte[8 * 1024];
                int s;
                while ((s = in.read(buffer)) > 0) {
                    out.write(buffer, 0, s);
                }
                out.flush();
            }
        }
    }
}

O método extractNativeLibraries precisa ser executado antes do carregamento da DLL (é lógico). O importante aqui é o caminho onde a DLL será descompactada. Caso o caminho não esteja entre os caminhos de busca do sistema operacional (variável de ambiente PATH do Windows, por exemplo), é necessário modificar a variável java.library.path da JVM. Veja o artigo java.lang.UnsatisfiedLinkError para a solução deste problema.

Referências:

Nenhum comentário:

Postar um comentário