sexta-feira, 1 de junho de 2012

JSP - Java Servlets

O que são Java Servlets?


Servlets são a solução em Java para programação CGI. São nada mais que programas em Java que trabalham em servidores Web para gerar páginas Web. Tal qual PHP, Python, ASP, .NET e mais uma infinidade de linguagens. Os motivos de usá-las são os mesmos para se usar qualquer outra tecnologia para CGI:

  • A página é baseada em conteúdo enviado pelo usuário - Por exemplo, as páginas de resultados de um sistema de busca que são geradas em tempo de execução.
  • Os dados mudam com muita frequência - Por exemplo, páginas de notícias que precisam sempre enviar conteúdo novo, ou mesmo, um blog.
  • A página web usa informações de bancos de dados ou de outras fontes - Por exemplo, em e-commerces para a listagem dos produtos a venda com preços e quantidade disponível.



Sem o uso de CGI não teríamos como gerar páginas em tempo de execução, ou seja, as páginas teriam que estar previamente preparadas para cada requisição específica. Teríamos que republicar páginas inteiras para manter os sites atualizados. E nem poderíamos sequer pensar em utilizar bancos de dados em aplicações web. Para quem não sabe, hoje em dia, quase qualquer site usa banco de dados. De tamanhas as vantagens tanto em termos de segurança quanto de facilidade.

Quanto a JSP, é uma tecnologia que permite misturar Java com conteúdo HTML. Como ocorre com PHP, ASP e muitas mais. Um lado bom é que ela permite que o processamento seja implementado tanto dentro da página HTML como fora dela. Claro que por questões de organização é sempre melhor o processamento fora das páginas e só enviar o resultado para elas.


O que é necessário para executar uma Servlet?

Basta você ter instalado e configurado um software servidor para isso. Existem vários para executar servlets Java. Os principais são Apache Tomcat, JBoss, Jetty, GlassFish e o IBM WebSphere Application Server. No caso, utilizamos o Apache Tomcat 6 com mod_jk para conectá-lo a um servidor Apache 2 utilizando hosts virtuais.
Esses servidores simplesmente executam os códigos escritos na linguagem para que foram feitos, no caso Java, e retornam uma resposta para o cliente segundo os padrões web. Como páginas html, arquivos pdf, imagens, dados texto, json, etc.

Antes de começar a fazer servlets, vamos explicar como instalar e configurar o servidor Tomcat 6 com mod_jk.

Como instalar e configurar o Apache Tomcat 6 com hosts virtuais?


Bem, houve vários probleminhas técnicos para conseguir instalar e configurar o Tomcat 6 aqui em casa. Porém, depois de consultar o google a rodo. Encontrei diversas fontes úteis, mas o site que mais ajudou foi este aqui.
Para instalar o Apache Tomcat 6. É necessário que você tenha instalados o Java SE 6 ou o Java SDK 6. Aqui eles foram instalados seguindo o tutorial deste site que, além de ajudar bastante na instalação, resolveu meus problemas com o IcedTea - tornou-o desnecessário. Enfim.

Considero que você já tenha o java em sua máquina e se trate de um sistema Debian (caso não, será necessário adaptar os comandos). Com isso, primeiro precisamos instalar o Tomcat e o módulo apache para fazer a conexão com o Tomcat, o mod_jk.
Basta rodar estes comandos:
...
sudo apt-get install tomcat6 tomcat6-common
sudo apt-get install libapache2-mod-jk
sudo /etc/init.d/apache2 restart
...

A partir do Tomcat 5.5 a aplicação de administração não é mais instalada por padrão e, se você a quiser, ela precisa ser instalada a parte. Para instalá-la:
...
sudo apt-get install tomcat6-admin
...

E para acessá-la, teremos que encontrar onde se encontra o arquivo 'tomcat-users.xml' para definir os usuários que terão acesso.
...
sudo find / -iname '*tomcat-users.xml'
...
Obs: uso o sudo aqui para não ficar recebendo um monte de mensagens de erro de acesso a diretórios/arquivos.

No meu caso, o arquivo se encontrava em: /etc/tomcat6/tomcat-users.xml
Encontre no arquivo este código:
...



...


E e adicione o usuário e senha que desejar:
...


  
  
  

...
No caso adicionamos o usuário "admin" com senha "admin" e com privilégios de administrador.
Tutorial de como adicionar usuários ao Tomcat encontrado aqui.

Para reiniciar o Tomcat 6, temos que rodar o seguinte comando:
...
/etc/init.d/tomcat6 restart
...
E para não precisar ficar sempre tendo que lembrar qual a minha versão do Tomcat, criei um link simbólico genérico:
...
ln -s /etc/init.d/tomcat6 /etc/init.d/tomcat
...

Entre aqui http://localhost:8080/host-manager/html para acessar o admin do Tomcat, agora já configurado.
Recomendo que você instale também o pacote de documentações do Tomcat, o tomcat6-docs. Pois ele ajuda bastante embora não seja necessário.


Feito isso, temos que criar/configurar uma aplicação jsp.
As aplicações seguem a seguinte estrutura:
Imagem copiada daqui: http://www.mhavila.com.br/topicos/java/tomcat.html#t05_01
Podendo ter uma pasta "src" no diretório superior para armazenar os fontes usados nas aplicações.

Existem dois jeitos básicos para criar uma aplicação: através de um arquivo .WAR ou manualmente. Como não entendo quase nada de aplicações JSP e de como gerar um arquivo .WAR, vou continuar manualmente.
De qualquer forma, você pode criar sua aplicação em qualquer lugar que preferir. Claro que é mais recomendável que você o faça de forma organizada. Eu criei a minha no diretório padrão do Tomcat 6: /var/lib/tomcat6/webapps/
...
sudo mkdir /var/lib/tomcat6/webapps/web
sudo mkdir /var/lib/tomcat6/webapps/web/WEB-INF
sudo mkdir /var/lib/tomcat6/webapps/web/WEB-INF/classes
sudo mkdir /var/lib/tomcat6/webapps/web/WEB-INF/lib
sudo touch /var/lib/tomcat6/webapps/web/WEB-INF/web.xml
sudo touch /var/lib/tomcat6/webapps/web/index.jsp
sudo chmod 777 -R /var/lib/tomcat6/webapps/web #linha so para facilitar (jamais faca isto em producao)
...
Nota: o web.xml tem sempre o mesmo nome independente do projeto. Numa aplicação chamada quack, ele continua a se chamar web.xml. Ok? (Parece besteira, mas é esse tipo de detalhe que costuma ser o mais difícil de se descobrir e corrigir.)

Feito isto, precisamos dar conteúdo para o web.xml e para o index.jsp

Para o web.xml vamos dar o seguinte conteúdo:
...



 Aplicacao exemplo
 
 
  index.jsp 
 


...
Este arquivo pode ser muito mais elaborado, mas isso aqui já é o suficiente para o básico do projeto funcionar.

Para o conteúdo do index.jsp eu coloquei o seguinte para testar:
...
<%@ page import="java.io.File" %>




 
  
  Projeto JSP
 
 
  Este projeto está armazenado na pasta <%= getServletContext().getRealPath("") + System.getProperty( "file.separator" ) %>
  
  




  Os sapinhos fazem hum ah hummmmm!

  Os sapinhos fazem hum ah hummmmm!

  Os sapinhos fazem hum ah hummmmm!

 

...



Estamos quase lá. Só falta criar o host virtual no admin do Tomcat (http://localhost:8080/host-manager/html) e o seu projeto deve começar a funcionar. Entre no admin do Tomcat preencha os dados como na imagem a seguir:

Obs: CATALINA_BASE é uma variável do Tomcat que aponta para o diretório onde foi instalado, no caso /var/lib/tomcat6/. E sua aplicação já é para estar funcionando. :D

Código exemplo de uma página do gerador de boletos gratuitos do boleto bancário:
...
<%@ page import="com.boletobancario.boleto.html.BoletoHTMLFormatter" %><%@
    page import="com.boletobancario.boleto.BoletoFactory" %><%
    
    
 // Exemplo utilizando dados estaticos. Na aplicacao real os dados devem ser atribuidos
 // dinamicamente.
 BoletoFactory factory = new BoletoFactory();
 
 factory.setBanco( BoletoFactory.BRADESCO );
 // Pode ser passado tambem o numero do banco no lugar da constante. Ex:
 //factory.setBanco( "237" );
 factory.setAgencia( "123" );
 factory.setCedente( "4567" );
 factory.setCarteira( "06" );
 factory.setNossoNumero( "378" );
 factory.setValor( "189,55" );
 factory.setVencimento( "30/04/2012" );
 
 
 factory.setNomeCedente( "Empresa Demonstração LTDA." );
 factory.setLocalPagamento( "Pagável em qualquer agência bancária até o vencimento." );
 
 factory.setNomeSac( "Beltrano de Tal" );
 factory.setEnderecoSac( "R. Silas Salazar, 768 - 8º Andar" );
 
 factory.setCepSac( "12345678" );
 
 factory.setCidadeSac( "São Paulo" );
 factory.setEstadoSac( "SP" );
 
 factory.setMensagem( 1, "Após o vencimento, entre em contato com nossa central de atendimento: [b]0800-00001[/b]." );
 
 out.print( BoletoHTMLFormatter.asSlimWebPage( factory.createBoleto() ) );
%>
...
Sim, eu aprendi o básico de JSP só para montar uma aplicação para gerar boletos. Pois ainda não há nenhuma ferramenta confiável para isso em Python. =/


Vamos fazer uma servlet

Para começar, vamos precisar do .jar para servlets. No caso do Tomcat 6 é preciso o Servlet Api 2.5. Ele já é instalado junto com o Tomcat. No meu computador o encontrei em /usr/share/java/servlet-api-2.5.jar. Por meio do seguinte comando:
...

sudo find / -iname '*servlet*'

...
Obs: o sudo é só para não ficar recebendo mensagens de erros de permissão de acesso que acabam por esconder os resultados de verdade do find. Imagino que haja algum parâmetro para isso.

Já explicamos como configurar o servidor e criar um projeto. Com isso, vamos continuar considerando que você já tem um projeto funcionando.

Uma servlet, na prática, é só uma classe feita a partir da classe HttpServlet. Aqui temos uma servlvet bem básica (que me salvou a pele). Ela DEVE fazer parte de um pacote (package) e estar dentro de uma pasta com mesmo nome.
...

package mypackage;

import java.io.*;

import javax.servlet.http.*;
import javax.servlet.*;

public class HelloServlet extends HttpServlet
{
 public void doGet (HttpServletRequest req, HttpServletResponse res)
 throws ServletException, IOException
 {
  PrintWriter out = res.getWriter();
  out.println("Hello, Brave new World!");
  out.close();
 }
}

...
Mas não é só isso o que precisamos para ela funcionar. Quando compilá-la seu .class deve ser salvo na pasta WEB-INF/classes/mypackage. Se o nome do pacote fosse, por exemplo, com.xyz.mypackage, teríamos que salvar em WEB-INF/classes/com/xyz/mypackage.

Caso tenha tido algum problema ao compilá-la, pode ser que precise definir o caminho para os .jar no classpath do javac. Aqui, tive que importar
...

javac HelloServlet.java -classpath servlet-api-2.5.jar

...

Já num caso mais elaborado tive que importar dois .jar. Coisa que o javac não estava querendo aceitar, até que encontrei a resposta:
...

#em linux se usa os dois pontos para separar, no windows e so trocar para ponto e virgula
javac HelloServlet.java -classpath Bopepo-20110415-bin.jar:servlet-api-2.5.jar

...


A classe estando no lugar correto, só precisamos definir qual a URL vai dar acesso a ela. Para quem trabalha com Django, uma servlet seria um similar as views e as urls são definidas no arquivo web.xml (similar ao urls.py, mas faz muito mais). Aqui temos o código para do web.xml do meu projeto:
...



 Gerador de Boletos - www.boletobancario.com
 
 
  index.jsp
 

    
 This is a simple web application with a source code organization
 based on the recommendations of the Application Developer's Guide.
    
    
     
        helloServlet
        mypackage.HelloServlet
    

     
        helloServlet 
        /helloservlet
    


...

Tudo feito, agora só falta reiniciar o servidor para as mudanças fazerem efeito:
...

sudo /etc/init.d/tomcat6 restart

...
E pronto, temos uma servlet bem básica funcionando. ;)

Quack!



Bem, tudo que foi feito funcionou muito bem localmente. Agora, é preciso enviar isso para um servidor ubuntu e configurar para que seja possível acessar usando uma url como, por exemplo, http://www.umenderecosoparaexemplo.com.br ao invés de http://[MEU_IP]/meuprojeto.
Além disso, no servidor temos vários sites funcionando como Virtual Hosts. O que faz com que esses sites tenham todos o mesmo IP, só mudando qual vai ser acessado conforme a url de acesso.

Como configurar Apache2 + Tomcat6 via mod_jk para usar Hosts Virtuais?

Para integrar o sistema do Apache com o Tomcat, precisamos ter instalado o pacote libapache2-mod-jk e configurar algumas coisinhas. Pois um não consegue se comunicar diretamente com o outro muito bem.

Para instalar o mod_jk é só usar o apt-get:
sudo apt-get install libapache-mod-jk
E teremos o o módulo instalado. Para configurar as coisas precisaremos criar o workers.properties. Esse arquivo armazena os detalhes a respeito de cada processo do Tomcat. Estes processos são definidos como workers, os quais se comunicam através do protocolo ajpv13.

Para clarear um pouco, um worker do Tomcat é uma instância do Tomcat que fica na escuta e espera para executar servlets ou outros conteúdos de algum servidor. No nosso caso, vamos ter o servidor Apache encaminhando requisições para intâncias do Tomcat (workers) que estão trabalhando "nos bastidores".
Tendo isso claro em mente, não é difícil de concluir que cada worker se refere a um dos seus projetos JSP armazenados no seu Tomcat. O que significa também que você pode criar diversos workers nesse arquivo para ter seus diversos projetos funcionando num único servidor na forma de hosts virtuais.


Vamos precisar descobrir qual a JVM que está sendo utilizada, para isso, este comando - descoberto aqui - lista todas as disponíveis e qual a que está sendo utilizada:
sudo update-alternatives --config java 
No meu caso, a JVM usada foi /usr/lib/jvm/java-6-openjdk/bin/java.

E também precisamos saber onde o Tomcat foi instalado. No meu caso foi em /var/lib/tomcat6

Para a integração funcionar, teremos que configurar algumas coisinhas. Para começar, encontre o arquivo workers.properties. No meu caso, ele estava neste diretório: /etc/libapache2-mod-jk/workers.properties. Se você instalou a mesma versão do mod_jk por apt-get é para estar aí. Se não, é só dar um find. Aqui vamos precisar apontar para a JVM e para a raiz do Tomcat:
workers.tomcat_home=/var/lib/tomcat6
workers.java_home=/usr/lib/jvm/java-6-openjdk
ps=/
worker.list=exemplo
worker.exemplo.port=8009
worker.exemplo.host=localhost
worker.exemplo.type=ajp13
worker.exemplo.lbfactor=1

Após criar este arquivo, crie um arquivo jk.conf dentro da pasta de módulos do Apache: /etc/apache2/mods-available/jk.conf com o seguinte conteúdo:
JkWorkersFile /etc/libapache2-mod-jk/workers.properties
JkLogFile /var/log/apache2/mod_jk.log
JkLogLevel debug

Crie um link simbólico para o apache iniciar o mod_jk junto com ele:
sudo ln -s /etc/apache2/mods-available/jk.conf  /etc/apache2/mods-enabled/jk.conf

Agora, vamos criar a configuração de host virtual. Ela pode ser acrescentada no final do arquivo httpd.conf:
sudo vim /etc/apache2/httpd.conf
Ou em seu sistema de hosts virtuais. De uma forma ou de outra, o código é este:
<VirtualHost *:80>
ServerName localhost
DocumentRoot /var/www/
JkMount /* exemplo
</VirtualHost>

Agora, temos que configurar o Tomcat para tudo funcionar corretamente.
Apague o arquivo /var/lib/tomcat6/conf/server.xml e recrie-o com este conteúdo:
<?xml version='1.0' encoding='utf-8'?>
<Server port="8005" shutdown="SHUTDOWN">

  <Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />

  <Listener className="org.apache.catalina.core.JasperListener" />
  <Listener className="org.apache.catalina.mbeans.ServerLifecycleListener" />
  <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />

  <Listener className="org.apache.jk.config.ApacheConfig" append="false"
            forwardAll="true" modJk="/usr/lib/apache2/modules/mod_jk.so"
            configHome="/usr/lib/apache2/modules/" />
  <GlobalNamingResources>
    <Resource name="UserDatabase" auth="Container"
              type="org.apache.catalina.UserDatabase"
              description="User database that can be updated and saved"

              factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
              pathname="conf/tomcat-users.xml" />
  </GlobalNamingResources>

  <Service name="Catalina">

    <Connector port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" />

    <Connector protocol="org.apache.coyote.http11.Http11NioProtocol"

           port="8443" enableLookups="true" disableUploadTimeout="true"
           acceptCount="100"  maxThreads="200"

           scheme="https" secure="true" SSLEnabled="true"
           keystoreFile="/var/lib/tomcat6/conf/certs/key.cert"
           keystorePass="umasenha"

           clientAuth="false" sslProtocol="TLS"/>

    <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />

    <Engine name="Catalina" defaultHost="localhost">

      <Realm className="org.apache.catalina.realm.UserDatabaseRealm"
             resourceName="UserDatabase"/>

      <Host name="localhost"  appBase="webapps"
            unpackWARs="true" autoDeploy="true"
            xmlValidation="false" xmlNamespaceAware="false">

      </Host>
    </Engine>
  </Service>
</Server>
Este código tem algumas diferenças do original que obtive aqui, pois surgiram alguns erros.


Feito isso, execute estes comandos (fonte):
sudo mkdir /var/lib/tomcat6/conf/certs/
sudo chmod 755 -R /var/lib/tomcat6/conf/certs/
sudo keytool -v -genkey -alias abc -keyalg RSA -keysize 2048 -sigalg SHA1withRSA -keypass umasenha -keystore /var/lib/tomcat6/conf/certs/key.cert
Ele gera um arquivo de certificado necessário, olhe nos atributos de um dos últimos Connectors. Note também que o atributo "keystorePass" e o parâmetro "keypass" devem ser iguais e mudados tanto no server.xml quanto no comando.

Bem, aqui já deveríamos ter tudo funcionando redondinho. Mas no meu servidor as coisas não acabaram bem e "fácil" assim. Houve ainda, algumas coisinhas por resolver:
  1. Setting property 'minSpareThreads' to '5' did not find a matching property.
  2. Setting property 'maxSpareThreads' to '75' did not find a matching property.
  3. WARNING: configured file:[/home/csmadmin/tools/tomcat/apache-tomcat-6.0.20/conf/certs/key.cert] does not exist.
  4. SEVERE: Parse Fatal Error at line 29 column 3: The element type "user" must be terminated by the matching end-tag "".
  5. SEVERE: Parse Fatal Error at line 29 column 3: The element type "role" must be terminated by the matching end-tag "".
  6. java.lang.RuntimeException: Unable to create path to config file :/usr/share/tomcat6/conf/auto/mod_jk.conf
  7. java.lang.RuntimeException: Unable to create path to config file :/usr/lib/apache2/modules/conf/auto/mod_jk.conf
  8. java.lang.RuntimeException: Unable to create path to config file :/usr/lib/apache2/modules/conf/jk/workers.properties
  9. java.lang.RuntimeException: Unable to create path to config file :/usr/lib/apache2/modules/logs/mod_jk.log

As soluções para cada um:
  1. Remoção de todas as ocorrências do atributo "minSpareThreads" em server.xml.
  2. Remoção de todas as ocorrências do atributo "maxSpareThreads" em server.xml.
  3. Criar um arquivo de certificado como explicado mais acima e aqui.
  4. Tag "user" não havia sido fechada com um "</user>" ou em si mesma "<user ... />" em tomcat-users.xml.
  5. Tag "role" não havia sido fechada com um "</role>" ou em si mesma "<role ... />" em tomcat-users.xml.
  6. Criar um link simbólico no local /usr/share/tomcat6/conf/auto/mod_jk.conf para o arquivo /etc/apache2/mods-available/jk.conf.
  7. Criar um link simbólico no local /usr/lib/apache2/modules/conf/auto/mod_jk.conf para o arquivo /etc/apache2/mods-available/jk.conf.
  8. Criar um link simbólico no local /usr/lib/apache2/modules/conf/jk/workers.properties para o arquivo /etc/libapache2-mod-jk/workers.properties.
  9. Criar um link simbólico no local /usr/lib/apache2/modules/logs/mod_jk.log para o arquivo /var/log/apache2/mod_jk.log.

E, finalmente, reinicie o apache e o tomcat.
sudo /etc/init.d/apache2 restart
sudo /etc/init.d/tomcat6 restart

Espero que tudo corra bem pra você, pois ficar revirando as pastas /var/log/tomcat6 e /var/log/apache2 torra o saco de qualquer um! ><
Aliás, se fosse só isso até que seria bom. O pior é procurar soluções e ver o povo só enrolando, inventando desculpas e tirando o corpo... Poxa, se errou: assuma e pronto! Acabou!

Enfim, com essas configurações ainda não é possível acessar utilizando o virtual hosts. Com essa configuração foi possível somente acessar usando IP:8080/nomedoprojeto. O que já é um progresso enorme perto de onde estava.

Depois de diversos montes alguns testes e fuçar bastante pela net, consegui configurar meu tomcat para funcionar com virtual hosts. A página que ajudou a achar a resposta foi esta aqui.
Para funcionar, mudei o "VirtualHost" para:
<VirtualHost *:80>
ServerName www.seudominio.com.br
DocumentRoot /var/lib/tomcat6/webapps/exemplo
JkMount /* exemplo
</VirtualHost>
No passo a passo sugerimos colocar esse código no fim do /etc/apache2/httpd.conf (ou junto a seu sistema de hosts virtuais, que foi meu caso).

Depois disso, é preciso adicionar o seu host ao /var/lib/tomcat6/conf/server.xml. O trecho de código a ser adicionado é este aqui:
      <Host name="exemplo" appBase="webapps/exemplo" unpackWARs="true" autoDeploy="true">
            <Context path="/" docBase="" reloadable="true" />
            <Alias>www.seudominio.com.br</Alias>
      </Host>
Lembrando que ele deve ser adaptado para o seu projeto em questão! Esse código deve ser inserido dentro desta tag:
<Engine name="Catalina" defaultHost="localhost">

O seu projeto já deverá funcionar sendo acessado por www.seudominio.com.br. XD
Porém, suas servlets e urls definidas no web.xml não.
Calma, é só criar um arquivo context.xml na raiz do seu projeto com este conteúdo:
<?xml version='1.0' encoding='utf-8'?>
<Context>
    <WatchedResource>WEB-INF/web.xml</WatchedResource>
</Context>
E, agora, finalmente mesmo. Está tudo funcionando! \o/

Gostaria muito de agradecer ao Anderson Moreira, dono deste blog http://siep.ifpe.edu.br/anderson/blog/?p=733 que divulgou a solução em que me baseie por quase completo, e ao Thiago Soares que foi quem a implementou. Sem essa implementação com certeza levaria mais de um ou dois meses para eu conseguir configurar o server corretamente.
Claro que sempre surgem uns erros "inexplicáveis" como:

Error 500 (of doom from hell)

Caso você venha a ter um erro 500 insolúvel na sua aplicação, tente remover os .jar da pasta lib do projeto e os acrescentar um por um de novo, reiniciando o server a cada adição. Acabei de apanhar um tanto pra descobrir isso (um .jar desnecessário foi parar na pasta só pra dar problemas). ><

A linda mensagem de amor do Tomcat para mim nesta manhã:
...
HTTP Status 500 -

type Exception report

message

description The server encountered an internal error () that prevented it from fulfilling this request.

exception

org.apache.jasper.JasperException: java.lang.NullPointerException
 org.apache.jasper.servlet.JspServletWrapper.handleJspException(JspServletWrapper.java:527)
 org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:359)
 org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:313)
 org.apache.jasper.servlet.JspServlet.service(JspServlet.java:260)
 javax.servlet.http.HttpServlet.service(HttpServlet.java:717)

root cause

java.lang.NullPointerException
 org.apache.jsp.index_jsp._jspInit(index_jsp.java:24)
 org.apache.jasper.runtime.HttpJspBase.init(HttpJspBase.java:52)
 org.apache.jasper.servlet.JspServletWrapper.getServlet(JspServletWrapper.java:159)
 org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:329)
 org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:313)
 org.apache.jasper.servlet.JspServlet.service(JspServlet.java:260)
 javax.servlet.http.HttpServlet.service(HttpServlet.java:717)

note The full stack trace of the root cause is available in the Apache Tomcat/6.0.24 logs.
Apache Tomcat/6.0.24
...
Obs: o meu index.jsp termina na linha 23...

Agora que de fato, está tudo funcionando. Vamos continuar com alguns extras que podem vir a serem úteis.

Como importar classes num arquivo jsp?

Para importar classes no seu arquivo .jsp é só utilizar o seguinte código no topo do arquivo (primeira instrução antes mesmo da tag HTML e DOCTYPE):
...
<%@ page language="Java" import="boletos.*, java.io.File" %>
...
Este serve para importar múltiplas classes de múltiplos pacotes.

...
<%@ page language="Java" import="boletos.*" %>
...
Este serve para importar múltiplas classes de um pacote.
E para usar as classes no arquivo .jsp é praticamente a mesma coisa que num arquivo .java.
Lembre-se que suas classes devem estar compiladas no diretório class/nomedopacote do seu projeto.


Só um detalhe que faltava para conseguir trabalhar com Servlets/JSP: receber dados via GET e POST. É possível fazer isso, tanto usando JSP, quanto Servlets. Porém, é muito mais seguro, eficiente e diversos outros "entes" mais fazer isso via Servlet. Ainda mais se sua intenção for fazer um trabalho organizado seguindo um padrão MVC.

Segundo esteeste e este site é possível implementar isso dentro de um JSP, em uma Servlet ou usando um "bean". Novamente, o mais recomendado é dentro de uma Servlet.

Um exemplo básico de Servlet que recebe tanto dados via POST quanto via GET:
...

package exemplo;

import java.io.*;

import javax.servlet.http.*;
import javax.servlet.*;


public class HelloServlet extends HttpServlet
{
 public void doGet(HttpServletRequest request, HttpServletResponse response)
 throws ServletException, IOException
 {
  request.getParameter('username');
} public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.getParameter('username'); } } ...
O método doGet é chamado sempre que uma requisição do tipo GET é feita. Já o método doPost é invocado quando a chamada é via POST.

Para receber os dados enviados, tanto via POST quanto via GET, os métodos são os mesmos:

  • getParameterNames (javax.servlet.ServletRequest): retorna os nomes de todos os parâmetros recebidos
  • getParameterValues (javax.servlet.ServletRequest): retorna todos os valores dos parâmetros desta requisição
  • getParameter (javax.servlet.ServletRequest): retorna o valor de um parâmetro a partir do nome dele

O uso é o mesmo, em ambos os casos, ou seja, bem simples via Servlet. ;)


Para fazer dentro de um arquivo JSP, é basicamente a mesma coisa, mas você precisa distinguir os métodos. O método getMethod retorna qual o tipo de requisição e, para auxiliar, tem também o método getQueryString que retorna os dados enviados via GET. Novamente, não é uma boa prática fazer assim. Explicamos pois pode ser útil em algum projetinho pessoal para resolver as coisas mais rápido sem ter que ficar montando servlets e tudo mais.

Agora, um Bean, segundo a Wikipedia é:
"Um componente de software reutilizável que cumpre determinadas convenções de desenho e nomenclatura. As convenções permitem que os beans sejam facilmente combináveis para criar uma aplicação, usando ferramentas que compreendem as convenções."

Eu ia explicar aqui como usá-las, mas este blog explica bem como usá-los. Então, não tem necessidade de repeteco. ;)



JSP - Gerar strings aleatórias

Estava precisando gerar Strings alfanuméricas aleatórias em Java nas minhas servlets.
Pesquisei um pouco e encontrei uma solução: http://stackoverflow.com/a/43496
É uma biblioteca do Apache: org.apache.commons.lang. Ela possui uma classe chamada RandomStringUtils que serve justamente para esse propósito.
Dentre seus diversos métodos há um randomAlphanumeric, que faz exatamente o que eu queria. ;)

Por exemplo, para gerar uma string alfanumérica com 20 caracteres:
...
import org.apache.commons.lang.RandomStringUtils;
...
String aleatoria = RandomStringUtils.randomAlphanumeric(20);
...


JSP - Dicionários

Em python, temos uma estrutura de dados chamada dicionário. Ela funciona como um vetor, mas ao invés de usar números como label para cada valor, são usadas strings.
Estava precisando disso em Java, mas como faz tempo que não trabalhava com ele, sabia que havia alguma estrutura similar, mas não conseguia me lembrar qual. Algumas buscas e encontrei as Hashtables.
Não são tão simples como os dicionários, mas são muito mais flexíveis.
No caso das Hashtables, são declarados na definição do objeto quais serão as classes/tipos da label e do valor armazenado. Para esclarecer, um exemplo usando labels String apontando para Integer:
...
import java.util.Hashtable;
...
Hashtable&lt;String, Integer&gt; dados = new Hashtable&lt;String, Integer&gt;();
...


JSP - Criar Date a partir de String

Outro problema, estava enviando datas via POST. Qual classe converte a String para java.util.Date? Pois os métodos de Date para essa conversão estão descontinuados (deprecated). Pesquisei um pouco e cai nesta solução aquijava.text.SimpleDateFormat. É uma solução bem simples. Você passa para o SimpleDateFormat o formato da sua data em formato textual e ele faz a conversão para você. Veja o exemplo:


...
import java.text.SimpleDateFormat;
import java.util.Date;
...
String data = "2012-05-15 09:07:22";
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
Date date = sdf.parse(data);
...


JSP - Como passar valores através da URL

Em Django isso é corriqueiro e bem simples de fazer usando regex. Agora em JSP não encontrei muita coisa. Pelo menos não para servlets 2.5. Parece que as servlets 3.0 são muito melhores. Mas o servidor em que estou trabalhando não suporta Tomcat 7 para poder usar isso. De qualquer forma, achei esta solução aqui. Exemplo:


&lt;servlet-mapping&gt;
    &lt;servlet-name&gt;redteam&lt;/servlet-name&gt;
    &lt;url-pattern&gt;/red/*&lt;/url-pattern&gt;
&lt;/servlet-mapping&gt;

&lt;servlet-mapping&gt;
    &lt;servlet-name&gt;blueteam&lt;/servlet-name&gt;
    &lt;url-pattern&gt;/blue/*&lt;/url-pattern&gt;
&lt;/servlet-mapping&gt;
Ao acessar a URL seria algo como http://localhost:8080/blue/variaveis/para/usar/. A parte "variaveis/para/usar" pode ser pega usando request.getPathInfo(). No meu caso foi para pegar o valor do permalink gerado usando o RandomStringUtils.



Como conectar ao MySQL?

Para se comunicar com o MySQL, não é difícil. Com certeza devem haver bibliotecas que tornam tudo muito mais simples que fazer requisições com sql puro. Porém, para o que estava precisando isso é mais do que suficiente.

Algumas ressalvas antes de começar:
  • Se você for importar uma classe (de dentro do diretório /WEB-INF/classes), essa classe DEVE pertencer a um pacote. Não sei ao certo os motivos disso, mas se isso não for feito sua classe simplesmente não vai funcionar.
  • Não se esqueça de baixar o pacote Connector/J aqui para fazer a conexão e colocá-lo na pasta lib.
  • Documentação oficial do Connector/J aqui.

Aqui temos um exemplo de código feito usando a documentação oficial para conectar ao MySQL:
...
package exemplos;

import java.io.*;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.ResultSet;

import java.util.ArrayList;


public class Exemplo
{
 public void Exemplo()
 {
 }

 public String teste()
 {
  Connection conn = null;
  Statement stmt = null;
  ResultSet rs = null;
  String database = "seubancodedados";
  String user = "usuario";
  String password = "senha";

  conn = DriverManager.getConnection("jdbc:mysql://localhost/"+database+"?user="+user+"&password="+password);

  try
  {
      String saida = "";
      stmt = conn.createStatement();
      if (stmt.execute("SELECT * FROM tabela_exemplo"))
      {
          rs = stmt.getResultSet();
          while( rs.next() )
          {
           saida += rs.getString("id") + " | ";
          }
      }
      
      return saida;
  }
  catch (SQLException ex)
  {
      return "SQLException: " + ex.getMessage()+"\nSQLState: " + ex.getSQLState()+"\nVendorError: " + ex.getErrorCode();
  }
  finally
  {
         try
         {
          if( rs != null )
           rs.close();
             rs = null;
         }
         catch (SQLException sqlEx) { } // ignore
         try
         {
          if( stmt != null )
           stmt.close();
          stmt = null;
         }
         catch (SQLException sqlEx) { } // ignore
  }
  
  return "fim";
 }

}
...
Este arquivo pode ser utilizado sem alterações, desde que armazenado em classes/exemplos do seu projeto.
Concordo que não é melhor dos códigos, mas funciona. Você só precisa inserir nele os dados de acesso ao seu banco de dados e adaptar a recuperação dos dados para a sua necessidade.

E aqui está o código do meu arquivo index.jsp, que é o arquivo que faz a chamada a classe Exemplo:
<%@ page language="Java" import="exemplos.*, java.io.File" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="pt" lang="pt">
 <head>
  <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
  <title>Projeto JSP</title>
 </head>
 <body>
  Este projeto estã armazenado na pasta <strong><%= getServletContext().getRealPath("") + System.getProperty( "file.separator" ) %></strong>
  
  <br/><br/><br/>
  <% 
   Exemplo bol = new Exemplo();
   String teste = bol.teste(); 
   out.print(teste);
  %>
  <br/><br/><br/>

  Os sapinhos fazem hum ah hummmmm!<br/>
  Os sapinhos fazem hum ah hummmmm!<br/>
  Os sapinhos fazem hum ah hummmmm!<br/>
 </body>
</html>


JSP - Gerar boletos em Servlets


Finalmente chegamos aonde queria tanto chegar com essa série de posts. Pois aqui vamos resolver o problema que me obrigou a ter de aprender um pouco de JSP: gerar boletos utilizando servlets.
Considero que você saiba de que se trata uma servlet, como criar uma e configurá-la.

Bem, para gerar o boleto estou utilizando a biblioteca Jrimum-Bopepo, segundo conhecidos, muitas pesquisas pela internet e consultas a fóruns chegamos a ela. Tem também a BoletoBancarioFree, porém, ela é código fechado apesar de gratuita. E damos preferência para o open source, sempre. Ainda mais que ambas são de excelente qualidade.

O código para gerar o boleto é bem simples, aliás, é o disponível na página do Bopepo, só com uma correçãozinha. A classe Titulo.Aceite não existe, o correto é Titulo.EnumAceite. Só isso.

Quanto a servlet, tudo que é preciso fazer é definir o MimeType para application/pdf e retornar o arquivo pdf em anexo ao invés de uma página html.

Aqui temos a servlet pronta para gerar o boleto exemplo e retornar para o usuário baixar:
package mypackage;

import java.io.*;
import java.math.BigDecimal;
import java.util.Date;

import javax.servlet.http.*;
import javax.servlet.*;

import org.jrimum.bopepo.BancosSuportados;
import org.jrimum.bopepo.Boleto;
import org.jrimum.bopepo.view.BoletoViewer;
import org.jrimum.domkee.comum.pessoa.endereco.CEP;
import org.jrimum.domkee.comum.pessoa.endereco.Endereco;
import org.jrimum.domkee.comum.pessoa.endereco.UnidadeFederativa;
import org.jrimum.domkee.financeiro.banco.febraban.Agencia;
import org.jrimum.domkee.financeiro.banco.febraban.Carteira;
import org.jrimum.domkee.financeiro.banco.febraban.Cedente;
import org.jrimum.domkee.financeiro.banco.febraban.ContaBancaria;
import org.jrimum.domkee.financeiro.banco.febraban.NumeroDaConta;
import org.jrimum.domkee.financeiro.banco.febraban.Sacado;
import org.jrimum.domkee.financeiro.banco.febraban.SacadorAvalista;
import org.jrimum.domkee.financeiro.banco.febraban.TipoDeTitulo;
import org.jrimum.domkee.financeiro.banco.febraban.Titulo;
import org.jrimum.domkee.financeiro.banco.febraban.Titulo.EnumAceite;


public class HelloServlet extends HttpServlet
{
    /**
  * 
  */
 private static final long serialVersionUID = 1L;



 public static Boleto createBoleto()
    {
  //INFORMANDO DADOS SOBRE O CEDENTE.
        Cedente cedente = new Cedente("PROJETO JRimum", "00.000.208/0001-00");

        //INFORMANDO DADOS SOBRE O SACADO.
        Sacado sacado = new Sacado("JavaDeveloper Pronto Para Férias", "222.222.222-22");

        // Informando o endereço do sacado.
        Endereco enderecoSac = new Endereco();
        enderecoSac.setUF(UnidadeFederativa.RN);
        enderecoSac.setLocalidade("Natal");
        enderecoSac.setCep(new CEP("59064-120"));
        enderecoSac.setBairro("Grande Centro");
        enderecoSac.setLogradouro("Rua poeta dos programas");
        enderecoSac.setNumero("1");
        sacado.addEndereco(enderecoSac);

        //INFORMANDO DADOS SOBRE O SACADOR AVALISTA.

        SacadorAvalista sacadorAvalista = new SacadorAvalista("JRimum Enterprise", "00.000.000/0001-91");

        //Informando o endereço do sacador avalista.
        Endereco enderecoSacAval = new Endereco();
        enderecoSacAval.setUF(UnidadeFederativa.DF);
        enderecoSacAval.setLocalidade("Brasília");
        enderecoSacAval.setCep(new CEP("59000-000"));
        enderecoSacAval.setBairro("Grande Centro");
        enderecoSacAval.setLogradouro("Rua Eternamente Principal");
        enderecoSacAval.setNumero("001");
        sacadorAvalista.addEndereco(enderecoSacAval);

        //INFORMANDO OS DADOS SOBRE O TÍTULO.
        
        //Informando dados sobre a conta bancária do título.
        ContaBancaria contaBancaria = new ContaBancaria(BancosSuportados.BANCO_BRADESCO.create());
        contaBancaria.setNumeroDaConta(new NumeroDaConta(123456, "0"));
        contaBancaria.setCarteira(new Carteira(30));
        contaBancaria.setAgencia(new Agencia(1234, "1"));
        
        Titulo titulo = new Titulo(contaBancaria, sacado, cedente, sacadorAvalista);
        titulo.setNumeroDoDocumento("123456");
        titulo.setNossoNumero("99345678912");
        titulo.setDigitoDoNossoNumero("5");
        titulo.setValor(BigDecimal.valueOf(0.23));
        titulo.setDataDoDocumento(new Date());
        titulo.setDataDoVencimento(new Date());
        titulo.setTipoDeDocumento(TipoDeTitulo.DM_DUPLICATA_MERCANTIL);
        titulo.setAceite(EnumAceite.A);
        titulo.setDesconto(new BigDecimal(0.05));
        titulo.setDeducao(BigDecimal.ZERO);
        titulo.setMora(BigDecimal.ZERO);
        titulo.setAcrecimo(BigDecimal.ZERO);
        titulo.setValorCobrado(BigDecimal.ZERO);

        //INFORMANDO OS DADOS SOBRE O BOLETO.
        Boleto boleto = new Boleto(titulo);
        
        boleto.setLocalPagamento("Pagável preferencialmente na Rede X ou em " +
                        "qualquer Banco até o Vencimento.");
        boleto.setInstrucaoAoSacado("Senhor sacado, sabemos sim que o valor " +
                        "cobrado não é o esperado, aproveite o DESCONTÃO!");
        boleto.setInstrucao1("PARA PAGAMENTO 1 até Hoje não cobrar nada!");
        boleto.setInstrucao2("PARA PAGAMENTO 2 até Amanhã Não cobre!");
        boleto.setInstrucao3("PARA PAGAMENTO 3 até Depois de amanhã, OK, não cobre.");
        boleto.setInstrucao4("PARA PAGAMENTO 4 até 04/xx/xxxx de 4 dias atrás COBRAR O VALOR DE: R$ 01,00");
        boleto.setInstrucao5("PARA PAGAMENTO 5 até 05/xx/xxxx COBRAR O VALOR DE: R$ 02,00");
        boleto.setInstrucao6("PARA PAGAMENTO 6 até 06/xx/xxxx COBRAR O VALOR DE: R$ 03,00");
        boleto.setInstrucao7("PARA PAGAMENTO 7 até xx/xx/xxxx COBRAR O VALOR QUE VOCÊ QUISER!");
        boleto.setInstrucao8("APÓS o Vencimento, Pagável Somente na Rede X.");

  return boleto;
    }
 
 
 
 public void doGet (HttpServletRequest req, HttpServletResponse res)
 throws ServletException, IOException
 {
  Boleto boleto = createBoleto();
  BoletoViewer viewer = new BoletoViewer(boleto);
  byte[] pdfAsBytes = viewer.getPdfAsByteArray();

  res.setContentType("application/pdf");
  res.setHeader("Content-Disposition", "attachment; filename=boleto.pdf");

  OutputStream output = res.getOutputStream();
  output.write(pdfAsBytes);

  res.flushBuffer();
 }
}

Obs: foi para gerar esta classe que precisei importar dois .jar.



Espero que este post seja útil. ;)
Fontes: http://www.apl.jhu.edu/~hall/java/Servlet-Tutorial/ , http://www.visitflanders.be/docs/appdev/sample/ e http://stackoverflow.com/questions/219585/setting-multiple-jars-in-java-classpath.





Um comentário: