segunda-feira, 15 de dezembro de 2014

How to Assemble Multi-Module Project with Maven

Assembling multi-module projects with Maven can be tricky. Based on different forum posts and on books, I finally could set my project to assemble for distribution.

I created prototypes to validate my ideas, using Maven 3.2.3. For learning purposes, my prototype multi-module project is like this:

Application project's structure.
  • application: it contains the parent pom file with a few and important configurations.
  •  app-distribution: created only to assemble the whole project for distribution; it has no code and no resource, only pom file configuration, and an assembly descriptor file; this approach is mention as a 'low-tech' best practice.
  • app-gui: the graphical user interface; this is the main application actually; it has resources, some included in the jar file and some to be distributed outside the jar file.
  • app-model: imaginary application model with some resources to de distributed outside the jar file.
  • app-utils: imaginary utility classes.
Besides the app-distribution, the other modules have dependencies among them and on third-party libraries, like this:

Application project's dependencies.

Before assembling the project, let's take care of the resources that must go outside of the jar file; otherwise, they will be zipped inside the jar file since they are placed in the default \src\main\resources folder. The piece of code below belongs to the pom file of the app-gui and app-model modules to exclude files with 'ini' extension:

    <build>
        <resources>
            <resource>
                <directory>${project.basedir}/src/main/resources</directory>
                <excludes>
                    <exclude>**/*.ini</exclude>
                </excludes>               
                <filtering>false</filtering>               
            </resource>            
        </resources>
    </build>

To assemble the whole project, modules with resources that go outside of the jar file must be bundled first, that is, the app-gui and app-model modules. Those modules are bundled only with their external resources; their main jar file and libs are left out at this stage.

To make that work, an assembly descriptor file must be created, and the maven-assembly-plugin must be configured to do so for each module. For this project, that is not a big deal since we have only two modules to set up, but for large projects that might be cumbersome.

So, let's think of reuse!

Firstly, even before compiling the application projects, a new project come into play, created completely independent of the application project mentioned previously; the bundle project has only an XML file which is the assembly descriptor file. It is placed in the /src/main/resources/assemblies folder (that's important), and it is named 'bundle-descriptor.xml':

    <assembly>
        <id>bundle</id>
        <formats>
            <format>zip</format>
        </formats>
        <includeBaseDirectory>false</includeBaseDirectory>
        <fileSets>
            <!-- Add external resources -->
            <fileSet>
                <directory>src/main/resources/properties</directory>
                <outputDirectory>properties</outputDirectory>
                <useDefaultExcludes>true</useDefaultExcludes>
            </fileSet>
            <fileSet>
                <directory>src/main/resources/xsd</directory>
                <outputDirectory>xsd</outputDirectory>
                <useDefaultExcludes>true</useDefaultExcludes>
            </fileSet>
        </fileSets>
    </assembly>

Execute the following command to package and install the JAR file in maven repository before working on the application project:

mvn package install

The bundle-descriptor.xml file only handles external resources; it zips them in a file to be deployed to the maven repository. The module's jar file and libs are left out.

To make use of the bundled project, the maven-assembly -plugin has to be configured the same way for both app-model and app-gui modules. Instead, only one configuration can be placed in the parent pom, within <build> and <pluginanagement> tags, to be reused later by those modules:

    <build>
        <pluginManagement>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-assembly-plugin</artifactId>
                    <version>2.5.2</version>
                    <dependencies>
                        <dependency>
                            <groupId>blog.dacanal.assembly</groupId>
                            <artifactId>bundle</artifactId>
                            <version>1.0.0</version>
                        </dependency>
                    </dependencies>
                    <executions>
                        <execution>
                            <id>assemble</id>
                            <phase>package</phase>
                            <goals>
                                <goal>single</goal>
                            </goals>
                            <configuration>
                                <descriptorRefs>
                                    <descriptorRef>bundle-descriptor</descriptorRef>
                                </descriptorRefs>
                            </configuration>
                        </execution>
                    </executions>
                </plugin>
            </plugins>
        </pluginManagement>
    </build>

The bundle project is set as a dependency of the maven-assembly-plugin, and the assembly descriptor file name is set within the <descriptorRef> tags. That way, the bundle project and the configuration of the maven-assembly-plugin can be reused by those modules that make reference to the plugin in their pom file, simply like this:

    <build>
        <plugins>
            <plugin>
                <artifactId>maven-assembly-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

Now it's time to work on the app-distribution module. The whole application is distributed with the main jar file in the root directory, the other dependent modules and third party libs in the \lib directory, and the external resources in their own directories.

Some dependencies have to be set in the pom file of the app-distribution module. The app-gui is the main module; it becomes a dependency here, and it has the complete dependency tree of the application. The external resources of the app-gui and app-model modules are recovered by setting them as dependencies with <type> tag as 'zip' and <classifier> tag as 'bundle' (that's the id of the bundle-descriptor XML file).

    <dependencies>
        <dependency>
            <groupId>${project.groupId}</groupId>
            <artifactId>gui</artifactId>
            <version>${project.version}</version>
            <type>jar</type>
        </dependency>
        <dependency>
            <groupId>${project.groupId}</groupId>
            <artifactId>gui</artifactId>
            <version>${project.version}</version>
            <type>zip</type>
            <classifier>bundle</classifier>
        </dependency>
        <dependency>
            <groupId>${project.groupId}</groupId>
            <artifactId>model</artifactId>
            <version>${project.version}</version>
            <type>zip</type>
            <classifier>bundle</classifier>
        </dependency>
    </dependencies>

The app-distribution module has its own assembly descriptor XML file with a configuration to mount our application for distribution. The descriptor file handles all dependencies (including the dependency tree) of the app-distribution module, unpacking them, or placing them on specific directories. The code of the assembly descriptor file is presented below; I added some comments to it, explaining a bit of each set of <dependencySet> tag:

    <assembly>
        <id>distribution</id>
        <formats>
            <format>zip</format>
        </formats>
        <baseDirectory>${appName}</baseDirectory>
        <dependencySets>
            <!-- Unpack all dependencies of ZIP type and BUNDLE classifier,  -->
            <!-- and unpack them in the root folder.                         -->
            <dependencySet>
                <useProjectArtifact>false</useProjectArtifact>
                <useTransitiveDependencies>false</useTransitiveDependencies>
                <unpack>true</unpack>
                <includes>
                    <include>*:zip:bundle</include>
                </includes>
            </dependencySet>
            <!-- Get the main module and place it in the root dir. -->
            <dependencySet>
                <useProjectArtifact>false</useProjectArtifact>
                <useTransitiveDependencies>false</useTransitiveDependencies>
                <unpack>false</unpack>
                <includes>
                    <include>*:*:jar</include>
                </includes>
            </dependencySet>      
            <!-- Get all dependency tree libs and place them in \lib folder, -->
            <!-- except those of ZIP type and BUNDLE classifier, and the     -->
            <!-- the main module. -->
            <dependencySet>
                <useProjectArtifact>false</useProjectArtifact>
                <useTransitiveDependencies>true</useTransitiveDependencies>            
                <unpack>false</unpack>
                <outputDirectory>lib</outputDirectory>
                <excludes>
                    <exclude>*:zip:bundle</exclude>
                    <exclude>${project.groupId}:gui:*</exclude>
                </excludes>
            </dependencySet>
        </dependencySets>    
    </assembly>

The configuration of the maven-assembly-plugin cannot be reused here. Actually, it has to be avoided because it is unnecessary to get the app-distribution module assembled every time the whole project is compiled and packaged (the application is not intended to be distributed on a daily basis). Then, a new configuration is created in the pom file of the app-distribution module, as described below (with comments):

    <build>
        <plugins>           
            <!-- Plugin required to assemble the project for distribution  -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-assembly-plugin</artifactId>
                <version>2.5.2</version>
                <!-- Not attached to any execution phase; it can only be invoked with 'mvn assembly::assembly' command. -->
                <configuration>
                    <descriptors>
                        <descriptor>src/main/assemblies/assembly-descriptor.xml</descriptor>
                    </descriptors>
                    <finalName>${appName}-${project.version}</finalName>                 
                </configuration>
                <!-- No need to run every time this module is compiled and packaged. -->
                <!-- Overwrite the parent pom configuration.                         -->
                <executions>
                    <execution>
                        <id>assemble</id>
                        <phase>package</phase>
                        <goals>
                            <goal>single</goal>
                        </goals>
                        <configuration>
                            <skipAssembly>true</skipAssembly>
                        </configuration>
                    </execution>
                </executions>              
            </plugin>
        </plugins>
    </build>

To get the application assembled, the user has to type the maven command in the app-distribution directory:

mvn assembly:assembly

I've been using this approach to assemble and distribute applications at work. I hope I have made myself clear and hope this post can help others to tailor their own solution using Maven.

The code I developed to validate this approach is available for download on Google Drive.

Steps To Make It Work


1) For those who are new using maven: before compiling the project modules, the parent pom file must be deployed to the local repository; run this command line from the parent directory (\application):

mvn install:install-file -DgroupId=blog.dacanal -DartifactId=application -Dversion=1.0-SNAPSHOT -Dpackaging=pom -Dfile=pom.xml

2) Package and install the JAR file of the assembly project in maven repository (\assembly):

mvn package install

3) Now, the same for the application project (\application):

mvn package install

4) Enter the app-distribution directory (\application\app-distribution) and assembly the project:

mvn assembly:assembly

The app-project will be assembled in \application\app-distribution\target\application-1.0-SNAPSHOT-distribution folder.

quinta-feira, 23 de outubro de 2014

Java RMI

Apresento aqui um projeto simples de Java RMI para avaliar o mecanismo de comunicação entre objetos remotos no Java. Ele consiste no desenvolvimento de um projeto servidor e um projeto cliente, usando o padrão MVC (Model-View-Controler) para a comunicação.

Como os dois projetos compartilham interfaces e classes de dados, duas bibliotecas Java foram criadas, sendo:
  • broker: interfaces para o implementação do padrão MVC.
  • data: modelo de dados do projeto, que consiste em apenas uma classe.
Ambos os executáveis Java, cliente e servidor, fazem uso das duas bibliotecas. Todos os módulos do projetos estão estruturados pelo Maven, assim fica fácil abri-los tanto no NetBeans quanto no Eclipse, ou em outra ferramenta que interprete Maven.

Após executar o servidor, múltiplas instâncias do cliente podem (e devem) se abertas para teste. A interface gráfica é muito simples e permite o usuário manter uma lista de nomes apenas, ou seja, o usuário pode inserir, alterar ou excluir nomes desta lista. Um mecanismo de callback do servidor para os clientes foi implementado. Assim, uma ação executada num cliente automaticamente refletirá na interface gráfica dos demais. A Figura 1 apresenta a interface gráfica para o cliente Java RMI.


Figura 1 - Interface gráfica do cliente Java RMI.
Junto com o código disponível, está o modelo de classes implementado no projeto. Para ler o modelo, será necessário instalar o Astah Community. A Figura 2 apresenta o diagrama de classes do projeto, onde os quatro módulos do projeto, e suas classes e interfaces, são apresentados com cores distintas.

Figura 2 - diagrama de classes do projeto.

Abaixo, está disponível pelo Google Drive o código do projeto para download.

A execução não tem segredo; basta rodar primeiramente o servidor e depois várias instâncias do cliente. Não há necessidade de iniciar o Java Registry RMI porque há uma codificação no próprio servidor para fazer isso automaticamente.

Como existem vários blogs que descrevem códigos para implementação de servidor e cliente Java RMI, aqui não vou apresentar e nem comentar os códigos do projeto. O objetivo é apenas disponibilizar o código para que se possa rapidamente fazer funcionar o Java RMI.


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:

quarta-feira, 12 de fevereiro de 2014

java.lang.UnsatisfiedLinkError

O erro java.lang.UnsatisfiedLinkError é lançado quando se carrega uma uma DLL pelo java.lang.System.loadLibrary("mylibrary"). O problema pode ocorrer porque a JVM não consegue localizar o arquivo a ser carregado, conforme mostrado abaixo.

Exception in thread "main" java.lang.UnsatisfiedLinkError: no mylibrary in java.library.path
    at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1734)
    at java.lang.Runtime.loadLibrary0(Runtime.java:823)
    at java.lang.System.loadLibrary(System.java:1028)

Pesquisando na internet, logo se descobre que é necessário adicionar o caminho da DLL na variável de java.library.path. Para tanto, o aplicativo deveria ser inicializado assim:

java -Djava.library.path=/path/to/my/dll -jar application.jar

... além de outros argumentos para iniciar corretamente a aplicação Java.

Iniciar um aplicativo desta forma não é nada amigável. Ainda, existem outros casos que o caminho e até o nome da DLL precisam ser resolvidos dinamicamente (este era o meu caso).

Além da incialização da variável por linha de comando, existem várias outras soluções para o problema, como colocar a DLL num caminho que já esteja definido no java.library.path ou mesmo num caminho definido na variável de ambiente PATH (no Windows) do sistema operacional.

Todavia, alterar a variável java.library.path por código Java é uma opção mais viável e muito mais fácil. Esta informação não é nenhuma novidade e facilmente se encontra esta codificação pela Internet:

String javaLibPath = System.getProperty("java.library.path");
javaLibPath += ";" + newPath;
System.setProperty("java.library.path", javaLibPath); 

Uma vez a variável alterada pelo código do programa, antes do carregamento da DLL, se espera que o problema seja resolvido. Mas não, o erro UnsatisfiedLinkError continua aparecendo!

O problema persiste porque a JVM faz a leitura da variável na java.library.path na inicialização do aplicativo e atribui seus valores à uma outra variável interna chamada de  sys_paths. Mesmo que a variável java.library.path seja alterada posteriormente, a JVM não atualiza a sua variável interna.

Para fazer com que a JVM atualize novamente a sys_paths, o código abaixo precisa ser adicionado, após o código descrito anteriormente, para alterar o java.library.path:

final Field field = ClassLoader.class.getDeclaredField("sys_paths");
field.setAccessible(true);
field.set(null, null);

No próximo instante que a JVM fizer uso da sua variável interna sys_paths, estando ela com valor nulo, ela será então reiniciada com os valores da variável java.library.path.

Os créditos desta dica ficam para Fahd Shariff, autor do artigo Changing JavaLibrary Path at Runtime.

terça-feira, 4 de fevereiro de 2014

Upload de Múltiplos Arquivos

Estava desenvolvendo um protótipo e chegou num momento que precisava fazer upload de múltiplos arquivos de uma vez. O JSF não oferece esta opção ainda e nem mesmo o IceFaces. Por outro lado, o Primefaces oferece a melhor solução no momento, mas não optei por utilizar este framework devido a política que atualização adotada pela equipe de desenvolvimento.

Então, fiz pesquisas e encontrei soluções em HTML5, Javascript e Servlet, mas não tudo junto; faltava juntar os pedaços e os resultados são apresentados neste artigo. A imagem abaixo mostra como ficou a parte gráfica de upload de múltiplos arquivos.


Esta solução permite que vários arquivos sejam selecionados e enviados para o servidor e uma barra de progresso indica a evolução da transmissão dos dados, bem como o percentual enviado. A tabela com a lista de arquivos somente surge após a seleção dos arquivos e portanto ela é criada dinamicamente pelo Javascript. A barra de progresso também é atualizada pelo Javascript, mas trata-se de um elemento do HTML5.

Do lado do servidor, uma única Servelet é responsável pelo recebimento e persistência dos arquivos. Não farei comentários deste código que é de autoria John Yeary do blog Java Evangelist.

Do lado cliente, o Javascript originalmente foi codificado por Shiv Kumer do blog Matlus. O artigo deste propõe exatamente o que eu desejava, mas eu não fui capaz de replicar o trabalho de Shiv Kumar conforme descrito. Por este motivo, a partir do trabalho dele e de John Yeary, eu desenvolvi uma outra solução.

O resultado final ficou muito bom, porém merece ser testado em diversos navegadores. Há muito o que melhorar no código, mas já é um ótimo começo!

Download:

O código deste protótipo pode ser baixado pelo link abaixo. O projeto foi desenvolvido usando Maven, JEE 7, servidor Glassfish 4 e navegador Firefox 26.


Referências: