Capítulo 4

Melhorando a Usabilidade da Aplicação

4.1 Definindo o catálogo de filmes e a tela de detalhes

Para que os usuários possam escolher o filme para o qual desejam comprar ingressos, podemos disponibilizar uma tela que exibirá um catálogo de todos os filmes disponíveis.

Portanto, visando possibilitar que o usuário acesse essa tela, teremos que disponibilizar um novo mapeamento em nossa classe FilmeController e adicionar um novo link ao menu superior do sistema, o qual fará a requisição para o novo endereço mapeado:

  public class FilmeController{

    //Atributos omitidos

    @GetMapping("/filme/em-cartaz")
    public ModelAndView emCartaz(){
        ModelAndView modelAndView = new ModelAndView("filme/em-cartaz");

        modelAndView.addObject("filmes", filmeDao.findAll());

        return modelAndView;
    }

    //Demais métodos omitidos
  }

Ao ver nosso catálogo de filmes, o usuário naturalmente irá clicar em uma das opções e vai esperar que seja exibido mais detalhes sobre aquele filme, como, por exemplo, a duração, elenco, sinopse, banner e, até mesmo, uma breve avaliação do filme.

Para tanto, vamos criar essa nova tela que vai apresentar os detalhes do filme. A tela será composta basicamente por labels para descrever os dados que iremos inserir futuramente:


<div class=" col-md-6 col-md-offset-3">
    <h1>Titulo</h1>
    <image src="" />

    <div>
        <label for="ano">Ano</label>
        <span id="ano"></span>
    </div>

    <div>
        <label for="diretores">Diretores</label>
        <span id="diretores"></span>
    </div>

    <div>
        <label for="escritores">Escritores</label>
        <span id="escritores"></span>
    </div>

    <div>
        <label for="atores">Atores</label>
        <span id="atores"></span>
    </div>

    <div>
        <label for="descricao">Descrição</label>
        <span id="descricao"></span>
    </div>

    <div>
        <label for="avaliacao">Avaliação</label>
        <span id="avaliacao"></span>
    </div>

    <table class="table table-hover">
        <thead>
            <th>Sala</th>
            <th>Horario</th>
            <th>Ações</th>
        </thead>
        <tbody>
            <c:forEach items="${sessoes}" var="sessao">
                <tr>
                    <td>${sessao.sala.nome}</td>
                    <td>${sessao.horario}</td>
                    <td>
                        <a href="/sessao/${sessao.id}/lugares" class="btn">
                            Comprar
                            <span class="glyphicon glyphicon-blackboard" aria-hidden="true"></span>
                        </a>
                    </td>
                </tr>
            </c:forEach>
        </tbody>
    </table>                    
</div>

Por fim, precisamos fazer com que nosso controller disponibilize a lista de sessões do filme para a tela:


@GetMapping("/filme/{id}/detalhe")
public ModelAndView detalhes(@PathVariable("id") Integer id){
    ModelAndView modelAndView = new ModelAndView("/filme/detalhe");

    Filme filme = filmeDao.findOne(id);
    List<Sessao> sessoes = sessaoDao.buscaSessoesDoFilme(filme);

    modelAndView.addObject("sessoes", sessoes);

    return modelAndView;
}

Aprenda se divertindo na Alura Start!

Na Alura Start você vai criar games, apps, sites e muito mais! É hora de transformar suas ideias em programas de verdade! Suas animações e programas também te ajudam a conhecer mais de biologia, matemática e como as coisas funcionam. Estude no seu ritmo e com a melhor didática. A qualidade da conceituada Alura, agora para Starters.

Conheça os cursos online da Alura Start!

4.2 Exercício - Criando o catálogo de filmes e tela de detalhes do filme com sessões para compra

  1. Altere a classe FilmeController e adicione o método emCartaz:

     @GetMapping("/filme/em-cartaz")
     public ModelAndView emCartaz(){
         ModelAndView modelAndView = new ModelAndView("filme/em-cartaz");
    
         modelAndView.addObject("filmes", filmeDao.findAll());
    
         return modelAndView;
     }
    
  2. Altere o arquivo src/main/webapp/WEB-INF/tags/template.tag e remova o comentário para que no menu tenha um link para acesso a /filme/em-cartaz:

     <div class="collapse navbar-collapse"
             id="bs-example-navbar-collapse-1">
         <ul class="nav navbar-nav navbar-right">
    
             <li><a href="/admin/filmes">Filmes</a></li>
             <li><a href="/admin/salas">Salas</a></li>  
             <li><a href="/filme/em-cartaz">Comprar</a></li>                       
    
         </ul>
     </div>
    
  3. Vamos criar o método buscaSessoesDoFilme em SessaoDao:

     public List<Sessao> buscaSessoesDoFilme(Filme filme) {
             return manager.createQuery("select s from Sessao s where s.filme = :filme", Sessao.class)
                     .setParameter("filme", filme)
                     .getResultList();
     }
    
  4. Injete SessaoDao no FilmeController para poder buscar as sessões:

     @Controller
     public class FilmeController {
    
         @Autowired
         private FilmeDao filmeDao;
         @Autowired
         private SessaoDao sessaoDao;
    
         .
         .
         .
     }
    
  5. Vamos criar uma action em FilmeController para atender as requisições GET para /filme/{id}/detalhe:

     @GetMapping("/filme/{id}/detalhe")
     public ModelAndView detalhes(@PathVariable("id") Integer id){
         ModelAndView modelAndView = new ModelAndView("/filme/detalhe");
    
         Filme filme = filmeDao.findOne(id);
         List<Sessao> sessoes = sessaoDao.buscaSessoesDoFilme(filme);
    
         modelAndView.addObject("sessoes", sessoes);
    
         return modelAndView;
     }
    
  6. Reinicie a aplicação e acesse a página de filmes em cartaz. Clique em algum filme disponível e visualize a tela de detalhes do mesmo.

4.3 Trazendo dados reais para nossa aplicação

Nossa tela de detalhes já está criada (src/main/webapp/WEB-INF/views/filme/detalhe.jsp), no entanto, nós ainda não possuímos as informações necessárias relativas ao filme selecionado para passar para ela. Para nos ajudar a fazer isso, existe uma API bem conhecida que pode ser consumida, a OMDB.

A primeira coisa que é necessário fazermos é a requisição. O próprio Spring já tem uma classe específica que pode nos ajudar, a classe RestTemplate. Para podermos utilizá-la, basta a instanciarmos:


RestTemplate client = new RestTemplate();

Agora, é necessário apenas fazermos a requisição:


DetalhesDoFilme detalhesDoFilme = client.getForObject(url, DetalhesDoFilme.class);

Vamos entender melhor o que aconteceu nessa última linha! Deixamos claro para o RestTemplate que ele precisa fazer uma requisição do tipo Get e que essa requisição irá devolver um objeto que nós temos, nesse caso o DetalhesDoFilme.

A classe DetalhesDoFilme vai representar a resposta e, por isso, precisamos informar o que ela deve conhecer. Em outras palavras, precisamos informar como fazer o binding das chaves da resposta para cada atributo dela. Para facilitar esse trabalho, usaremos uma anotação que o Spring já utiliza por trás dos panos e é provida pelo Jackson, um especialista em interpretar e criar jsons.

public class DetalhesDoFilme {

    @JsonProperty("Title")
    private String titulo;

    @JsonProperty("Year")
    private Integer ano;

    @JsonProperty("Poster")
    private String imagem;

    @JsonProperty("Director")
    private String diretores;

    @JsonProperty("Writer")
    private String escritores;

    @JsonProperty("Actors")
    private String atores;

    @JsonProperty("Plot")
    private String descricao;

    @JsonProperty("imdbRating")
    private Double avaliacao;

    // getters e setters
}

Voltando para nossa requisição, precisamos saber se realmente houve resposta, porque pode ser que aquela API não tenha nenhum dado sobre o filme que estamos fazendo a busca. Portanto, vamos usar a mesma ideia de Optional para seguir com a estrutura do projeto.

DetalhesDoFilme detalhesDoFilme = client.getForObject(url, DetalhesDoFilme.class);

Optional.of(detalhesDoFilme);

Existe também a possibilidade de fazer a requisição e ter algum problema, seja indisponibilidade da API, estarmos sem internet ou qualquer outro problema nessa requisição. Por conta disso, é necessário precaver esse comportamento que gerará uma Exception e, nesse caso, também precisaremos definir um retorno para o usuário. Como estamos trabalhando com Optional, devolveremos um Optional vazio:


try {

    DetalhesDoFilme detalhesDoFilme = client.getForObject(url, DetalhesDoFilme.class);

    Optional.of(detalhesDoFilme);   

} catch(RestClientException e){

    Optional.empty();
}

Por fim, precisamos deixar esse comportamento isolado dentro um método:


public Optional<DetalhesDoFilme> request(Filme filme) {

    RestTemplate client = new RestTemplate();

    String url = // endereço de onde estamos obtendo as informações

    try {
        DetalhesDoFilme detalhesDoFilme = client.getForObject(url, DetalhesDoFilme.class);
        return Optional.of(detalhesDoFilme);
    } catch (RestClientException e) {
        return Optional.empty();
    }
}

Ainda é necessário deixarmos esse nosso método em alguma classe que precisa ser gerenciada pelo Spring, para isso usaremos a anotação @Component que faz com que a classe se torne um Bean.


@Component
public class ImdbClient {


    public Optional<DetalhesDoFilme> request(Filme filme) {

        RestTemplate client = new RestTemplate();


        String url = // endereço de onde estamos obtendo as informações

        try {
            DetalhesDoFilme detalhesDoFilme = client.getForObject(url, DetalhesDoFilme.class);
            return Optional.of(detalhesDoFilme);
        } catch (RestClientException e) {

            return Optional.empty();
        }
    }
}

Só temos um problema! Até agora, se tivermos qualquer problema nessa requisição, não saberemos o que nos levou a esse ponto. Poderíamos fazer um simples println da Exception, mas seria bem chato de conseguir o identificar no console, principalmente em projetos grandes, onde existam vários usuários e, eventualmente, algumas falhas. Nesses casos, é comum o uso de alguma biblioteca específica para log de sistemas. A boa notícia é que no mundo Java temos uma bastante conhecida e utilizada, chamada Log4J.

Vamos começar a utiliza-la em nosso sistema e, para isso, precisamos fazer a definição do objeto que ficará logando as coisas para nós:


import org.apache.log4j.Logger;

@Component
public class ImdbClient {

    private Logger logger = Logger.getLogger(ImdbClient.class);

    //restante

}

Nessa linha, declaramos que o Logger será responsável pela classe ImdbClient. Sendo assim, basta deixarmos nosso Logger fazer o trabalho dele:


catch (RestClientException e){
    logger.error(e.getMessage(), e);
    return Optional.empty();
}

Nesse caso, ele irá gerar um log sempre que ocorrer uma RestClientException e, além disso, podemos o utilizar para fazer debug, passar informação e mais algumas opções.

4.4 Exercício - Consumindo serviço para detalhes do filme

  1. Crie a classe DetalhesDoFilme no pacote br.com.caelum.ingresso.model para representar os detalhes do filme retornado pela API https://imdb-fj22.herokuapp.com/imdb?title=rogue+one:

     public class DetalhesDoFilme {
    
         @JsonProperty("Title")
         private String titulo;
    
         @JsonProperty("Year")
         private Integer ano;
    
         @JsonProperty("Poster")
         private String  imagem;
    
         @JsonProperty("Director")
         private String diretores;
    
         @JsonProperty("Writer")
         private String escritores;
    
         @JsonProperty("Actors")
         private String atores;
    
         @JsonProperty("Plot")
         private String descricao;
    
         @JsonProperty("imdbRating")
         private Double avaliacao;
    
         // getters e setters
     }
    
  2. Vamos criar uma classe que irá consumir o serviço web. Crie a classe ImdbClient no pacote br.com.caelum.ingresso.rest:

     import org.apache.log4j.Logger;
    
     @Component
     public class ImdbClient {
    
         private Logger logger = Logger.getLogger(ImdbClient.class);
    
         public  Optional<DetalhesDoFilme>  request(Filme filme){
    
             RestTemplate client = new RestTemplate();
    
             String titulo = filme.getNome().replace(" ", "+");
    
             String url = String.format("https://imdb-fj22.herokuapp.com/imdb?title=%s", titulo);
    
             try {
                 DetalhesDoFilme detalhesDoFilme = client.getForObject(url, DetalhesDoFilme.class);
                 return Optional.of(detalhesDoFilme);
             }catch (RestClientException e){
                 logger.error(e.getMessage(), e);
                 return Optional.empty();
             }
         }
    
     }
    
  3. Vamos alterar a action detalhes, na classe FilmeController, para consumir o serviço e disponibilizar na página as informações do filme. Para isso, precisamos de um objeto do tipo ImdbClient disponível nessa classe:

    • Injete ImdbClient no FilmeController:

        @Controller
        public class FilmeController {
      
            @Autowired
            private FilmeDao filmeDao;
      
            @Autowired
            private SessaoDao sessaoDao;
      
            @Autowired
            private ImdbClient client;
      
            .
            .
            .
        }
      
    • Altere a action de detalhes para obter as informações do filme através do atributo client e disponibilize-os para a página de detalhe:

        @GetMapping("/filme/{id}/detalhe")
        public ModelAndView detalhes(@PathVariable("id") Integer id){
            ModelAndView modelAndView = new ModelAndView("/filme/detalhe");
      
            Filme filme = filmeDao.findOne(id);
            List<Sessao> sessoes = sessaoDao.buscaSessoesDoFilme(filme);
      
            Optional<DetalhesDoFilme> detalhesDoFilme = client.request(filme);
      
            modelAndView.addObject("sessoes", sessoes);
            modelAndView.addObject("detalhes", detalhesDoFilme.orElse(new DetalhesDoFilme()));
      
            return modelAndView;
        }
      
  4. Reinicie a aplicação e verifique se os detalhes do filme foram obtidos corretamente através do consumo do serviço.

Seus livros de tecnologia parecem do século passado?

Conheça a Casa do Código, uma nova editora, com autores de destaque no mercado, foco em ebooks (PDF, epub, mobi), preços imbatíveis e assuntos atuais.
Com a curadoria da Caelum e excelentes autores, é uma abordagem diferente para livros de tecnologia no Brasil.

Casa do Código, Livros de Tecnologia.