Neste artigo veremos em detalhes como fazer uso do componente photoCam presente na biblioteca Primefaces. Esse componente é utilizado para capturar fotos através da webcam, o que facilita muito o registro de perfis em sistemas onde uma foto deve ser inserida.

A questão é que o uso do photoCam está circundada por diversos outros fatores que precisam ser entendidos e configurados para que só então possamos usar o photoCam.

Construindo o projeto para o PhotoCam

Primeiramente precisamos de um projeto Web usando o Maven, através do tipo maven-archetype-webapp. Assim, a estrutura padrão de um projeto web será criada automaticamente. Vamos iniciar verificando as bibliotecas necessárias para uso do PhotoCam. Na Listagem 1 segue no 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.springlab</groupId>
     <artifactId>springlab</artifactId>
     <packaging>war</packaging>
     <version>1.0-SNAPSHOT</version>
     <name>Springlab</name>
     <url>http://maven.apache.org</url>
     <properties>
           <org.springframework.version>3.0.6.RELEASE
           </org.springframework.version>
     </properties>

     <dependencies>

           <dependency>
                  <groupId>org.omnifaces</groupId>
                  <artifactId>omnifaces</artifactId>
                  <version>2.1</version>
           </dependency>

           <dependency>
                  <groupId>org.jboss.weld.servlet</groupId>
                  <artifactId>weld-servlet</artifactId>
                  <version>2.2.9.Final</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>

           <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>

     </dependencies>

     <repositories>
           <repository>
                  <id>prime-repo</id>
                  <name>PrimeFaces Maven Repository</name>
                  <url>http://repository.primefaces.org</url>
                  <layout>default</layout>
           </repository>
     </repositories>

     <build>
           <finalName>springlab</finalName>
           <plugins>
                  <plugin>
                         <groupId>org.apache.maven.plugins</groupId>
                         <artifactId>maven-pmd-plugin</artifactId>
                         <version>2.5</version>
                         <configuration>
                                <targetJdk>1.6</targetJdk>
                         </configuration>
                  </plugin>

                  <plugin>
                         <groupId>org.apache.maven.plugins</groupId>
                         <artifactId>maven-compiler-plugin</artifactId>
                         <version>3.1</version>
                         <configuration>
                                <source>1.6</source>
                                <target>1.6</target>
                         </configuration>
                  </plugin>

           </plugins>
           <resources>
                  <resource>
                         <directory>src/main/resources</directory>
                  </resource>
           </resources>
     </build>
</project>
Listagem 1. pom.xml

Veja que usamos pelo menos duas bibliotecas para utilizar o PhotoCam: JSF e Primefaces.

O é obrigatório para que a biblioteca do Primefaces seja carregada corretamente, sendo assim adicionamos esse pedaço do código:


<repositories>
     <repository>
            <id>prime-repo</id>
            <name>PrimeFaces Maven Repository</name>
            <url>http://repository.primefaces.org</url>
            <layout>default</layout>
     </repository>
</repositories>

Caso contrário, um erro no pom.xml será mostrado, apontando que a biblioteca do primefaces não pode ser “baixada” do repositório padrão do Maven.

O próximo passo é criar o arquivo web.xml dentro de main/webapp/WEB-INF com o conteúdo da Listagem 2.


<?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>Springlab</display-name>
 
 
       <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>
 
       <context-param>
             <param-name>javax.faces.DATETIMECONVERTER_DEFAULT_TIMEZONE_
             IS_SYSTEM_TIMEZONE</param-name>
             <param-value>true</param-value>
       </context-param>
 
       <welcome-file-list>
             <welcome-file>index.xhtml</welcome-file>
       </welcome-file-list>
 
       <context-param>
             <param-name>javax.faces.PROJECT_STAGE</param-name>
             <param-value>Development</param-value>
       </context-param>
 
       <context-param>
             <param-name>primefaces.THEME</param-name>
             <param-value>bootstrap</param-value>
       </context-param>
 
       <context-param>
             <param-name>com.sun.faces.writeStateAtFormEnd</param-name>
             <param-value>false</param-value>
       </context-param>
 
</web-app>
Listagem 2. web.xml

Pode dar qualquer nome ao projeto, mas no nosso caso colocamos:


<display-name>Springlab</display-name>

Configuramos o Primefaces para usar o tema “bootstrap”, por isso fizemos a importação deste tema no pom.xml. O welcome-file-list diz ao JSF qual arquivo deverá ser carregado como página principal ao acessar o path do projeto no browser.

Se você desenvolvesse sua página xhtml e seu ManagedBean neste ponto e tentasse usar o componente photoCam ou qualquer outro componente do Primefaces você teria o seguinte erro no console do browser:


Failed to load resource: the server responded with a status of 404 
(Not Found) http://localhost:8080/springlab/
javax.faces.resource/primefaces.css.xhtml?ln=primefaces

Acontece que é adicionado o caminho javax.faces.resource antes dos resources como primefaces.css, primefaces.js e etc., e para que isso funcione você teria que mudar o caminho em todas as referências, adicionando o javax.faces.resource. A solução para isso é usar um handler capaz de capturar esse caminho e realizar a conversão necessária para achar o resource.

Em outras palavras, você teria que mudar as suas referências de:


url("images/background.png");

para:


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

E isso porque estamos nos referindo apenas ao CSS. A ideia de adicionar um handler é evitar que precisemos realizar essa alteração. Poderíamos desenvolver um handler próprio para realizar essa função, mas a biblioteca OmniFaces já provê tal recurso, então vamos ganhar tempo utilizando-o. Para isso precisamos importar a mesma no pom.xml:


<dependency>
   <groupId>org.omnifaces</groupId>
   <artifactId>omnifaces</artifactId>
   <version>2.1</version>
</dependency>

Depois disso você poderá usar a classe org.omnifaces.resourcehandler.UnmappedResourceHandler responsável por realizar esse filtro. Para que ela funcione você precisa adicionar no arquivo faces-context.xml da Listagem 3.


<?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>
Listagem 3. faces-context.xml

Pronto, a partir deste ponto a classe UnmappedResourceHandler irá ser responsável por realizar o filtro que desejamos. Atente-se ao fato de que a configuração a seguir mostrada no web.xml é obrigatória:


<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>

Sem o url-pattern apontando para javax.faces.resource, a classe UnmappedResourceHandler não teria muita utilidade.

Ainda não terminamos a configuração do projeto, pois se você tentar iniciá-lo agora verá o erro no console da Listagem 4.


Grave: Exception sending context initialized event to listener instance of class     
org.omnifaces.ApplicationListener
java.lang.ExceptionInInitializerError
       at org.omnifaces.ApplicationListener.checkCDIAvailable(ApplicationListener.java:63)
       at org.omnifaces.ApplicationListener.contextInitialized(ApplicationListener.java:55)
       at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:5016)
       at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5524)
       at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
       at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1575)
       at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1565)
       at java.util.concurrent.FutureTask.run(FutureTask.java:262)
       at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
       at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
       at java.lang.Thread.run(Thread.java:744)
Caused by: java.lang.IllegalStateException: CDI API is not available in this environment.
       at org.omnifaces.config.BeanManager.<init>(BeanManager.java:88)
       at org.omnifaces.config.BeanManager.<clinit>(BeanManager.java:49)
       ... 11 more
Caused by: java.lang.ClassNotFoundException: javax.enterprise.inject.spi.BeanManager
       at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1720)
       at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1571)
       at java.lang.Class.forName0(Native Method)
       at java.lang.Class.forName(Class.java:190)
       at org.omnifaces.config.BeanManager.<init>(BeanManager.java:82)
       ... 12 more
Listagem 4. Erro no console – Faltando CDI

Lendo o erro apresentado você já consegue identificar a seguinte falta:


CDI API is not available in this environment.

Significa que você obrigatoriamente precisa do CDI no seu projeto para que o OmniFaces funcione. Em linhas gerais, o CDI é uma especificação do Java EE 6 que realiza e gerencia a injeção de dependências. O CDI precisa de alguém que o implemente, assim como o Hibernate implementa o JPA, então optamos por usar o Weld. Por isso adicionamos a seguinte linha no pom.xml:


<dependency>
    <groupId>org.jboss.weld.servlet</groupId>
    <artifactId>weld-servlet</artifactId>
    <version>2.2.9.Final</version>
</dependency>

Precisamos não somente adicionar o weld ao projeto, mas configurá-lo para funcionamento. Para isso, precisamos de um arquivo context.xml na pasta webapp/META-INF (se a pasta não existir, crie-a), como mostra a Listagem 5.


<Context>
    <Resource name="BeanManager"
        auth="Container"
        type="javax.enterprise.inject.spi.BeanManager"
        factory="org.jboss.weld.resources.ManagerObjectFactory" />
</Context>
Listagem 5. Conteúdo do arquivo context.xml

Após isso, precisamos também de um arquivo chamado beans.xml, que ficará em webapp/WEB-INF. Este arquivo inicialmente não terá nenhum conteúdo, pois precisamos apenas que ele seja criado.

Finalmente podemos iniciar nosso projeto sem erros e começar a usar o PhotoCam. Primeiramente precisamos criar um ManagedBean que receberá a foto capturada pelo PhotoCam e irá salvar em algum diretório, como mostra a Listagem 6.


package br.com.springlab;
 
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
 
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ViewScoped;
import javax.faces.context.FacesContext;
import javax.imageio.ImageIO;
 
import org.primefaces.event.CaptureEvent;
 
@ManagedBean(name = "fotoMB")
@ViewScoped
public class FotoMB {
         
   private static final String PATH_TO_SAVE = "/home/ronaldo/Downloads/";

   public void webcamCapture(CaptureEvent captureEvent) {
       try {
                byte[] data = captureEvent.getData();
                InputStream in = new ByteArrayInputStream(data);
                BufferedImage fotoBuffered = ImageIO.read(in);
                String idImagem = salvarImagemFromBufferedImage(fotoBuffered);

                System.out.println("Imagem gerada = " + idImagem);

       } catch (Exception e) {
                e.printStackTrace();                    
                FacesContext.getCurrentInstance().validationFailed();
       }
   }

   private String salvarImagemFromBufferedImage(BufferedImage bf) {
       String id = gerarIdImagem();
       try {
                ImageIO.write(bf, "png", new File(PATH_TO_SAVE + id + ".png"));

                return id;
       } catch (IOException e) {
                e.printStackTrace();
                return "";
       }
   }

   private String gerarIdImagem() {
     SimpleDateFormat simpleDateFormat = new 
     SimpleDateFormat("ddMMyyyyhhmmss");
     String id = simpleDateFormat.format(new Date());
     return id;
   }
}
Listagem 6. ManagedBean FotoMB

Vamos entender o ManagedBean em detalhes: o método gerarIdImagem() tem como principal objetivo gerar um nome único para imagem, assim não corremos o risco de sobrescrever alguma que foi gerada anteriormente. Para isso, ele monta o nome da imagem baseado no tempo, levando em consideração até os segundos. Raramente teremos mais de uma imagem gerada no mesmo segundo, mas se você quiser poderá levar em consideração os milissegundos ou mesmo usar outra técnica, como UUID (identificador único universal imutável, que representa um valor de 128 bits).

O outro método auxiliar é o salvarimagemfrombufferedimage(), responsável por receber um objeto BufferedImage e salvar em disco a imagem. Perceba que para isso ele usa em tese duas linhas:

  • String id = gerarIdImagem(); - para gerar o nome da nova imagem;
  • ImageIO.write(bf, "png", new File(PATH_TO_SAVE + id + ".png")); - para salvar a imagem em disco no formato png e no caminho especificado em PATH_TO_SAVE.

Por último, temos o método webCamCapture(), que o XHTML irá chamar no ManagedBean. Este converte a imagem que foi capturada para um objeto do tipo BufferedImage que possa ser recebido pelo nosso método salvarimagemfrombufferedimage(). A imagem que é capturada pelo objeto CaptureEvent está em bytes (byte[]):


byte[] data = captureEvent.getData();

Usamos o InputStream para ler esta e depois o ImageIO.read() para conversão em BufferedImage.

Nosso ManagedBean FotoMB.java tem duas anotações que são obrigatórias:


@ManagedBean(name = "fotoMB")
@ViewScoped

A primeira define o nome que utilizaremos para referenciá-lo no XHTML, neste caso será fotoMB e o escopo da classe. Usamos o ViewScoped, pois o estado da classe é armazenado enquanto o usuário estiver naquela página. Quando a página é alterada esse ManagedBean é destruído e na próxima visita a esta página um novo é construído.

Falta apenas uma última etapa: criar a página XHTML com o photoCam, como mostra a Listagem 7.


<!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:p="http://primefaces.org/ui">
 
<h:head>
</h:head>
<h:body>
  <h:form id="formWebcamPaciente" enctype="multipart/form-data">
      <p:photoCam widgetVar="pc" listener="#{fotoMB.webcamCapture}" />

      <p:toolbar>
         <p:toolbarGroup align="left">
            <p:commandButton value="Capturar" onclick="pc.capture()" />
         </p:toolbarGroup>
      </p:toolbar>
  </h:form>
 
</h:body>
</html>
Listagem 7. Usando photocam no xhtml

Para usarmos os componentes do JSF e do Primefaces declaramos os imports no início da página XHTML, definindo assim que usamos a tag p para o Primefaces e a tag h para o JSF.

Dentro do colocamos o componente <p:photocam> que possui dois atributos:</p:photocam>

  • widgetVar: Nome de uma variável para referenciarmos esse componente mais a frente;
  • listener: Nome do método no managedBean que irá receber a imagem capturada.

A variável pc será usada para disparar o método capture() quando o botão for clicado:


<p:commandButton value="Capturar" onclick="pc.capture()" />

Quando o usuário clicar no botão Capturar a variável pc estará referenciando o componente photoCam e irá disparar o método capture() que automaticamente chama o listener que referencia um método no ManagedBean.

Neste artigo vimos como usar o componente photoCam para capturar fotos via Webcam e salvar em disco. Para chegarmos nesse ponto mostramos toda configuração necessário para o componente possa funcionar sem problemas.