Testando um FTP Wrapper com JavaServer Faces

Veja neste artigo como criar um Wrapper para o cliente FTP e acessá-lo via JSF.

Neste artigo construiremos um wrapper para um cliente FTP básico, com funções apenas de conectar, mudar o diretório e listar os arquivos presentes. Nosso wrapper poderá ser utilizado através de interface JSF que construiremos com ajuda do Primefaces, para que não tenhamos muito trabalho com layout e componentes customizados.

Um Wrapper pode servir para adicionar mais funcionalidades a determinado objeto ou mesmo encapsular as funcionalidades já existentes fornecendo um menor nível de acoplamento, o que é muito bom para sistemas extensíveis.

Temos no Java diversos exemplos de Wrappers nativos, por exemplo, a classe Double é um Wrapper para o tipo primitivo double, pois ela adiciona diversas funcionalidades a este tipo primitivo. Projeto

Em nosso projeto usaremos JSF para que o usuário posso executar os comandos básicos no cliente FTP, para isso precisamos primeiro configurar nosso projeto, e na Figura 1 temos a estrutura de diretório usado.

Figura 1. Estrutura do Projeto

Perceba que estamos usando Maven como gerenciador de dependências e nosso pom.xml contém as bibliotecas necessárias para funcionamento do nosso projeto, tais como JSF e Primefaces, como mostra a Listagem 1. Usamos o Primefaces apenas para dar um visual melhor a nossa interface, mas você poderá usar o JSF puro apenas para efeito didático.

Listagem 1. pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>br.com</groupId> <artifactId>lab</artifactId> <packaging>war</packaging> <version>0.0.1-SNAPSHOT</version> <name>lab Maven Webapp</name> <url>http://maven.apache.org</url> <dependencies> <dependency> <groupId>commons-net</groupId> <artifactId>commons-net</artifactId> <version>2.0</version> </dependency> <dependency> <groupId>org.omnifaces</groupId> <artifactId>omnifaces</artifactId> <version>1.11-M1</version> </dependency> <dependency> <groupId>com.sun.faces</groupId> <artifactId>jsf-api</artifactId> <version>2.1.6</version> </dependency> <dependency> <groupId>com.sun.faces</groupId> <artifactId>jsf-impl</artifactId> <version>2.1.6</version> </dependency> <dependency> <groupId>org.primefaces.extensions</groupId> <artifactId>primefaces-extensions</artifactId> <version>0.7.1</version> </dependency> <dependency> <groupId>org.primefaces</groupId> <artifactId>primefaces</artifactId> <version>3.5</version> </dependency> <dependency> <groupId>org.primefaces.themes</groupId> <artifactId>bootstrap</artifactId> <version>1.0.9</version> </dependency> </dependencies> <build> <finalName>lab</finalName> </build> </project>

Vamos explicar o uso das bibliotecas importadas:

Quando vamos mapear um recurso em nossa aplicação (por recurso entenda imagens, CSS, JavaScript e quaisquer outros arquivos externos), nós precisamos definir o caminho onde o mesmo se encontra, mas em se tratando de um projeto web, os recursos geralmente ficam alocados na pasta “WeContent/resources”. Por isso, vamos apontar este caminho em um arquivo CSS por exemplo, usando a seguinte estrutura:

body { background: url("#{resource['css/images/background.png']}"); }

Agora imagine se tivéssemos um layout Bootstrap, com mais de 500 imagens dispersas por vários arquivos CSS: pense no trabalho que você teria em mudar caminho por caminho. Pensando nisso, a classe UnmappedResourceHandler faz automaticamente o mapeamento de todos esses arquivos, adicionando o caminho “javax.faces.resource” ao seu caminho, que é exatamente o que o EL Expression # produz. Isso só é possível porque no arquivo web.xml adicionamos o url-pattern “/javax.faces.resource/*” e no faces-config.xml temos o resource-handler mapeado para a classe UnmappedResourceHandler.

Então, ao invés de usarmos o EL Expression no background:url, podemos deixar no padrão comumente utilizado:

body { background: url("images/background.png"); }

Já que citamos o faces-config.xml, segue na Listagem 2 como realizamos sua declaração.

Listagem 2. faces-config.xml

<?xml version="1.0" encoding="UTF-8"?> <faces-config version="2.1" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xi="http://www.w3.org/2001/XInclude" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_1.xsd"> <application> <resource-handler>org.omnifaces.resourcehandler.UnmappedResourceHandler</resource-handler> </application> </faces-config>

Para configuração do projeto falta apenas o web.xml então podemos começar a desenvolver nosso wrapper, como mostra a Listagem 3.

Listagem 3. web.xml

<?xml version="1.0" encoding="UTF-8"?> <web-app version="3.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID"> <display-name>LAB</display-name> <welcome-file-list> <welcome-file>index.xhtml</welcome-file> </welcome-file-list> <context-param> <param-name>primefaces.THEME</param-name> <param-value>bootstrap</param-value> </context-param> <!-- Configuracao DO JSF --> <servlet> <servlet-name>Faces Servlet</servlet-name> <servlet-class>javax.faces.webapp.FacesServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>Faces Servlet</servlet-name> <url-pattern>*.xhtml</url-pattern> <url-pattern>/javax.faces.resource/*</url-pattern> </servlet-mapping> </web-app>

São três pontos específicos configurados em nosso web.xml, primeiramente o arquivo inicial que será carregado ao acessar o link do projeto, em nosso caso o index.xhtml, em segundo lugar o tema usado pelo Primefaces, que é o BOOTSTRAP, e por último e mais importante são os arquivos que o JSF irá renderizar, neste caso, todos os com final *.xhml ou que tenham javax.faces.resource em seu path.

Vamos começar a construir a classe FtpWrapper, presente na Listagem 4.

Listagem 4. FtpWrapper.java

package br.com.lab; import java.io.IOException; import java.net.SocketException; import org.apache.commons.net.ftp.FTPClient; public class FtpWrapper { private static FtpWrapper instance; private FTPClient ftp; private FtpWrapper(){ this.ftp = new FTPClient(); } public static FtpWrapper getInstance(){ if (instance == null){ instance = new FtpWrapper(); } return instance; } public String currentDirectory(){ try { checkIfConected(); return ftp.printWorkingDirectory(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); return null; } } private void checkIfConected(){ if (!ftp.isConnected()){ throw new RuntimeException("Não está conectado ao FTP"); } } public void connect(String hostname){ try { if (ftp.isConnected()){ throw new RuntimeException("Já conectado"); } ftp.connect(hostname); } catch (SocketException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } public void login(String username, String password){ try { ftp.login(username, password); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } public void changeWorkingDirectory(String targetDir){ try { checkIfConected(); ftp.changeWorkingDirectory(targetDir); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } public String[] listNames(){ try { checkIfConected(); return ftp.listNames(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); return null; } } }

Nessa classe a ideia é que todos os métodos de um FTP sejam encapsulados pelo nosso wrapper, deixando transparente para quem o usa qual provider FTP está sendo usado, seja do pacote Apache ou do pacote “sun”. Assim, se decidirmos mais à frente mudar por algum motivo o provider do FTP então o único trabalho será refazer os pontos da classe FtpWrapper, mas para todo o resto da aplicação isso será totalmente transparente.

Para aqueles que usavam a classe FtpClient do pacote sun.net.ftp desde o Java 6, devem ter percebido que ouve uma mudança drástica desta classe a partir do Java 7 em diante, tornando-se quase impossível reaproveitar desenvolvimentos já realizados. Para quem teve a genialidade de usar um wrapper desde o começo então o impacto será menor.

Perceba que estamos usando um padrão de projeto Singleton para garantir apenas uma instância desta classe durante toda a aplicação. O atributo “ftp” é quem de fato faz a conexão com o provider que escolhemos, em nosso caso, o FTPClient do pacote “org.apache.commons.net.ftp”.

Todas as chamadas dos nossos métodos farão referência para o atributo “ftp”. Por exemplo, o método checkIfConected método checa se estamos conectados ao FTP, caso contrário, é lançada uma exceção para evitar qualquer tarefa com o FTP desconectado.

O método String currentDirectory primeiro faz chamada ao checkIfConected, que irá interromper a execução imediatamente se o FTP não estiver conectado, depois chamamos o printWorkingDirectory(), que irá retornar o diretório atual que estamos conectados no FTP, lembrando que esse comando pode alterar de acordo com o provider (classe FTP) utilizado.

De acordo com o “hostname” passado (por exemplo: ftp.seudominio.com.br) realizamos a conexão a este host, mas antes checamos se já não temos uma conexão estabelecida.

Depois de conectado realizamos o login através do usuário e senha passados.

Podemos também mudar o diretório que estamos visualizando, passando como parâmetro o “targetDir” que corresponde ao diretório destino.

Por último retornamos uma lista com o nome dos arquivos do diretório atual.

A alteração do provider implicará na mudança dos métodos obviamente, mas será muito menos impactante do que mudar toda a aplicação.

Vamos agora iniciar a criação do ManagedBean que irá fazer a interface entre a página HTML (em JSF) e o FtpWrapper, como mostra a Listagem 5.

Listagem 5. FtpMB.java

package br.com.lab; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import javax.faces.application.FacesMessage; import javax.faces.bean.ManagedBean; import javax.faces.bean.ViewScoped; import javax.faces.context.FacesContext; @ManagedBean(name = "ftpMB") @ViewScoped public class FtpMB { private String hostName, username, password, newDir; private List<String> arquivos; private void showMessage(String message) { FacesContext fc = FacesContext.getCurrentInstance(); fc.addMessage(null, new FacesMessage(message)); } public void conectar() { try { FtpWrapper.getInstance().connect(hostName); FtpWrapper.getInstance().login(username, password); newDir = FtpWrapper.getInstance().currentDirectory(); showMessage("Conectado com sucesso em " + hostName); } catch (Exception e) { showMessage(e.getMessage()); } } public void mudarDiretorio() { try { FtpWrapper.getInstance().changeWorkingDirectory(newDir); showMessage("Diretório alterado para "+newDir); } catch (Exception e) { showMessage(e.getMessage()); } } public void listarArquivos() { try { arquivos = new ArrayList<String>(Arrays.asList(FtpWrapper .getInstance().listNames())); showMessage("Arquivos carregados com sucesso"); } catch (Exception e) { showMessage(e.getMessage()); } } public String getHostName() { return hostName; } public void setHostName(String hostName) { this.hostName = hostName; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getNewDir() { return newDir; } public void setNewDir(String newDir) { this.newDir = newDir; } public List<String> getArquivos() { return arquivos; } public void setArquivos(List<String> arquivos) { this.arquivos = arquivos; } }

O nosso ManagedBean usa o nome “ftpMB” para ser referenciado na página JSF, além disso, usamos o @ViewScoped para garantir que o estado do objeto seja mantido enquanto o usuário não mudar de página.

São cinco os atributos que usaremos durante toda a aplicação:

O método showMEssage() irá mostrar determinada mensagem na localização da tag <h:messages> que veremos na página XHTML.

O método conectar() faz a conexão e login no FTP usando o FtpWrapper sempre com o método getInstance(), que retorna à instância atual do nosso wrapper, ou cria uma nova caso ainda não exista. Perceba que além da conexão ao FTP ainda retornamos o diretório atual através do método currentdirectory(), finalizando mostramos a mensagem através do showMessage().

O método mudarDiretorio permite a mudança do diretório atual para um novo diretório escolhido pelo usuário.

No método listarArquivos chamamos o “listNames()” do nosso Wrapper para capturar a lista com o nome dos arquivos no diretório atual, como precisamos de um List, então fazemos a conversão para tal usando o Arrays.asList(), pois o listNames() retorna um vetor do tipo String[]. Os últimos métodos são os getters e setters necessários para que o JSF consiga fazer o binding entre o valor do XHTML e o ManagedBean.

Enfim podemos criar a página XHTML que irá realizar as operações de conexão ao FTP, mudança de diretório e listagem de arquivos, como mostra a Listagem 6. Lembrando que nosso objetivo não é criar um cliente FTP completo, mas mostrar como o encapsulamento pode ser realizado e acessado através de uma interface JSF.

Listagem 6. index.xhtml

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:p="http://primefaces.org/ui"> <h:head> <title>LAB</title> </h:head> <h:body> <h:form prependId="false"> <h:messages /> <p:panelGrid columns="2"> <label>Host</label> <p:inputText value="#{ftpMB.hostName}" /> <label>Usuário</label> <p:inputText value="#{ftpMB.username}" /> <label>Senha</label> <p:password value="#{ftpMB.password}" /> <label>Diretório</label> <p:inputText value="#{ftpMB.newDir}" /> </p:panelGrid> <p:commandButton value="Conectar" actionListener="#{ftpMB.conectar}" update="@form" /> <p:commandButton value="Mudar diretório" actionListener="#{ftpMB.mudarDiretorio}" update="@form" /> <p:commandButton value="Listar Arquivos" actionListener="#{ftpMB.listarArquivos}" update="@form" /> <h:dataTable value="#{ftpMB.arquivos}" var="arq" id="dataTable"> <h:column> # </h:column> </h:dataTable> </h:form> </h:body> </html>

Dentro da tag <h:form> temos a tag <h:messages> que será responsável por mostrar as mensagens que estão vindo no ManagedBean, através do método showMessage() que mostramos anteriormente. O panelGrid contém quatro campos (host, usuário, senha e diretório) onde todos os valores digitados só são submetidos ao managedbean quando um dos botões a seguir são acionados:

O objetivo principal deste artigo foi demonstrar como é útil utilizar um wrapper para manter um menor nível de acoplamento no seu projeto. Em nosso caso usamos o JSF para fazer uso desse Wrapper, mas você poderia usar uma interface Android, Swing ou qualquer outro que sua regra de negócio exigir.

Ebook exclusivo
Dê um upgrade no início da sua jornada. Crie sua conta grátis e baixe o e-book

Artigos relacionados