Enviando e-mail com PL/SQL
Em continuidade a nossa série “as mil-e-uma utilidades do seu banco de dados Oracle”, agora você poderá descobrir como é fácil enviar e-mails a partir do PL/SQL e saber o quanto isto é produtivo.
Vamos lá, para o envio de e-mails é necessário que o pacote UTL_MAIL esteja instalado.
Conectando como sysdba na instância ORCL.
C:\>set oracle_sid=orcl C:\>sqlplus sys/oracle as sysdba
Instalando o pacote UTL_MAIL. Isto não é nenhuma novidade, mas a variável de ambiente %ORACLE_HOME% indica o caminho a qual o banco de dados foi instalado, neste caso, foi utilizada a versão 10.2.0 – C:\oracle\product\10.2.0\db_1. Os sub-diretórios, contém os pacotes necessários para o UTL_MAIL funcionar.
SQL> set serveroutput on SQL> @%ORACLE_HOME%/rdbms/admin/utlmail.sql SQL> @%ORACLE_HOME%/rdbms/admin/prvtmail.plb SQL> show errors;
Pronto, isto é o suficiente para o nosso próximo passo, a criação da procedure de envio de e-mails.
Crie uma package para agrupar todas as funcionalidades que podem ser comuns a diversos projetos, e reutilize o código quando necessário.
create or replace package PKG_BLOG_UTIL is -- Author : LUCIANO -- Created : 05/07/2009 PROCEDURE enviar_email_auth(p_USUARIO IN VARCHAR2, p_SENHA IN VARCHAR2, p_TO IN VARCHAR2, p_COPIA IN VARCHAR2, p_SUBJECT IN VARCHAR2, p_MESSAGE IN VARCHAR2); end PKG_BLOG_UTIL;
Veja agora a versão 1.0 desta implementação:
CREATE OR REPLACE PACKAGE BODY PKG_BLOG_UTIL IS PROCEDURE ENVIAR_EMAIL_AUTH(P_HOST IN VARCHAR2, P_USUARIO IN VARCHAR2, P_SENHA IN VARCHAR2, P_TO IN VARCHAR2, P_COPIA IN VARCHAR2, P_SUBJECT IN VARCHAR2, P_MESSAGE IN VARCHAR2) IS MAIL_CONN UTL_SMTP.CONNECTION; V_HEADER VARCHAR2(4000); CRLF VARCHAR2(2) := CHR(13) || CHR(10); -- quebra de linha BEGIN -- Abre a conexão MAIL_CONN := UTL_SMTP.OPEN_CONNECTION(P_HOST, 25); UTL_SMTP.HELO(MAIL_CONN, P_HOST); -- Faz a autenticação para envio de mensagem UTL_SMTP.COMMAND(MAIL_CONN, 'AUTH LOGIN'); UTL_SMTP.COMMAND(MAIL_CONN, UTL_RAW.CAST_TO_VARCHAR2(UTL_ENCODE.BASE64_ENCODE(UTL_RAW.CAST_TO_RAW(P_USUARIO)))); UTL_SMTP.COMMAND(MAIL_CONN, UTL_RAW.CAST_TO_VARCHAR2(UTL_ENCODE.BASE64_ENCODE(UTL_RAW.CAST_TO_RAW(P_SENHA)))); -- Prepara o cabeçalho de V_HEADER := 'Date:' || TO_CHAR(SYSDATE, 'dd Mon yy hh24:mi:ss') || CRLF || 'From:' || p_USUARIO || CRLF || 'Subject:' || p_SUBJECT || CRLF || 'To:' || p_TO || CRLF || 'Cc:' || p_COPIA || CRLF || CRLF || CRLF || p_MESSAGE; -- UTL_SMTP.MAIL(MAIL_CONN, '<' || P_USUARIO || '>'); UTL_SMTP.RCPT(MAIL_CONN, '<' || P_TO || '>'); UTL_SMTP.DATA(MAIL_CONN, V_HEADER); -- Fecha a conexão UTL_SMTP.QUIT(MAIL_CONN); END ENVIAR_EMAIL_AUTH; END PKG_BLOG_UTIL;
Algumas coisas podem ser melhoradas como, separar o corpo da mensagem do cabeçalho e utilizar o UTL_TCP.CRLF como separador de linhas no lugar da variável CRLF, veja como o código fica mais organizado:
/* APENAS UM TRECHO DO CÓDIGO */ UTL_SMTP.MAIL(MAIL_CONN, '<' || P_USUARIO || '>'); UTL_SMTP.RCPT(MAIL_CONN, '<' || P_TO || '>'); UTL_SMTP.open_data(MAIL_CONN); -- Prepara o cabeçalho UTL_SMTP.write_data(MAIL_CONN, 'From' || ': ' || p_USUARIO || UTL_TCP.CRLF); UTL_SMTP.write_data(MAIL_CONN, 'To' || ': ' || p_TO || UTL_TCP.CRLF); UTL_SMTP.write_data(MAIL_CONN, 'Cc' || ': ' || p_COPIA || UTL_TCP.CRLF); UTL_SMTP.write_data(MAIL_CONN, 'Subject' || ': ' || P_SUBJECT || UTL_TCP.CRLF); -- Escreve a mensagem UTL_SMTP.write_data(MAIL_CONN, UTL_TCP.CRLF || p_message); UTL_SMTP.close_data(MAIL_CONN);
Eu preferi adotar este segundo código como o mais recomendável, mesmo assim, alguns detalhes podem ser adaptados a sua realidade, por exemplo, criar uma constante para o Mail Host, e indicando que o envio de e-mails sempre será a partir daquele servidor ou fazer a sobrecarga da procedure – a reutilização é uma benção.
Veja que a assinatura da procedure abaixo é um overload do que foi mostrado acima, isto é um conceito de Orientação-a-Objetos suportado pelo PLSQL, permite que um método seja escrito com diversas assinaturas diferentes, e reaproveitando o código de maneira eficaz.
PROCEDURE ENVIAR_EMAIL_AUTH(P_USUARIO IN VARCHAR2, P_SENHA IN VARCHAR2, P_TO IN VARCHAR2, P_COPIA IN VARCHAR2, P_SUBJECT IN VARCHAR2, P_MESSAGE IN VARCHAR2) IS V_MAILHOST CONSTANT VARCHAR2(100) := 'pop.xxxx.com.br'; BEGIN enviar_email_auth(P_HOST => v_mailhost, P_USUARIO => P_USUARIO, P_SENHA => P_SENHA, P_TO => P_TO, P_COPIA => P_COPIA, P_SUBJECT => P_SUBJECT, P_MESSAGE => P_MESSAGE); END ENVIAR_EMAIL_AUTH;
A package completa, está disponível aqui para download.
Não envie spam!
Chamando o bash a partir do PL/SQL com Java Stored Procedure
Se você sabe utilizar o recursos do Java Stored Procedure em seu banco de dados, significa que você é uma pessoa abençoada.
A não tão popular JSP compartilha alguns dos recursos da plataforma Java com a linguagem estrutural PL/SQL, eu disse alguns, pois existem limitações. Muitos desenvolvedores se beneficiam do pacote java.io.* quando falamos de Java Stored Procedure, visto que os recursos são mais amplos, quando comparado ao famoso UTL_FILE.
Vamos realizar passo-a-passo utilizando o banco de dados Oracle 10g em ambiente windows, para a criação de uma Procedure, que irá chamar um programa Java (Java Stored Procedure), cujo este permitirá executar comandos do prompt (os mais antigos leiam Bash).
É provavel que você encontre no Google diversas outras maneiras de criar a Java Stored Procedures, porém, o proposito aqui é mais amplo, ajudando a criar todo o ambiente, vamos lá!
Primeiramente, o usuário de banco, owner do objeto JSP, deve ter alguns permissões para manipular os arquivos no sistema operacional. Neste exemplo no nosso schema será blog.
Informe o usuário (schema), tipo de acesso e, opcionalmente o diretório em que será permitida a manipulação do arquivo.
EXEC DBMS_JAVA.grant_permission('BLOG', 'java.io.FilePermission', '<<ALL FILES>>', 'read ,write, execute, delete');
EXEC Dbms_Java.Grant_Permission('BLOG', 'SYS:java.lang.RuntimePermission', 'writeFileDescriptor', '');
EXEC Dbms_Java.Grant_Permission('BLOG', 'SYS:java.lang.RuntimePermission', 'readFileDescriptor', '');
Feito isto, os objetos de banco são simples, crie o programa abaixo com o usuário blog.
create or replace and compile java source named Bash as
package com.lucianosilva.oracle;
import java.io.*;
import java.lang.*;
public class Bash {
public static void command(String command) throws Exception {
final Process process = Runtime.getRuntime().exec(command);
process.waitFor();
process.destroy();
}
public static String commandReturn(String command) throws Exception {
final Process process = Runtime.getRuntime().exec(command);
process.waitFor();
String retCode = Integer.toString( process.exitValue() );
if ( retCode.equals("0") ){
retCode = retCode + " Executado com sucesso.";
}else{
retCode = retCode + " Erro durante a execucao.";
}
process.destroy();
return retCode;
}
public static void executeAsynchronousCommand(String command) throws Exception {
Runtime.getRuntime().exec( command );
}
}
O método commandReturn não retorna o resultado do comando bash e sim uma mensagem informando se o comando foi bem executado ou não.
Agora vamos testar, ainda como blog, faça:
CREATE OR REPLACE FUNCTION EXECUTAR_COMAND_BASH(P_COMMAND IN VARCHAR2) RETURN VARCHAR2 AS LANGUAGE JAVA NAME 'com.lucianosilva.oracle.Bash.commandReturn(java.lang.String) return java.lang.String';
Isto é tudo! Um exemplo muito prático, sem dúvidas, já que dependendo da solução adotada a integração de procedures PLSQL com o crontab é muito comum e isto pode ajudar.
Sayonara
ToStringUtil() com Annotations versão 1.0.2
Dando continuidade a customização do toString(), certamente existirá aquele atributo que você não irá querer exibir no toString(), então seguindo a dica do @pauloprestes, resolvido com annotations:
Aqui está a interface para a anotação, o melhor seria utilizar o Target como FIELD, porém, como falei vamos exibir os atributos com base nos métodos públicos Getters.
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface NoToString{}
Na classe ToStringUtil foi apenas acrescentado uma regra para não exibir os métodos marcados com a anotação @NoToString, sendo:
// Nao exibir os métodos que estejam com a Annotação NoToString boolean isNotAnnotation = m.isAnnotationPresent(NoToString.class);
Realizei alguns testes com tipos complexos e funcionou, talvez você tenha alguma sugestão para melhorar este tipo de tratamento.
Baixe aqui a versão 1.0.2.
Namastê!
Automatizando o toString() versão 1.0.1
É comum sobrescrever o método toString() da classe Object, facilita em situações como quando é necessário saber o conteúdo de um Bean, por exemplo. Sobretudo, reescrever este método poderá ser trabalhoso se você tem muitos atributos e cansativo, se você o fizer para todos os seu beans.
Pensando nisso resolvi criar uma classe útil que mostre todos os atributos Privates da sua classe juntamente com seus respectivos valores. Veja o código abaixo, em um bean eu reescrevi o método toString() fazendo um looping em todos os atributos:
public class Equipamento implements Serializable {
private String modelo;
private String marca;
private String descricao;
private Date fabricacao;
/*** getters/setters ocultados **/
public String toString(){
StringBuffer b = new StringBuffer();
// Obtem todos os atributos da classe
Field[] fields = this.getClass().getDeclaredFields();
b.append( this.getClass().getName() );
b.append( " { \n" );
for (int i = 0; i < fields.length; i++) {
try {
Field field = fields[i];
b.append( field.getName() );
b.append( " = " );
b.append( field.get(this) );
b.append("\n");
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
b.append( " } " );
return b.toString();
}
}
A reescrita do método acima resolve parcialmente o nosso problema, além de não atender aos tipos de dados complexos, você terá que fazer o famoso copy-paste para todas as classes que forem implementa-lo.
Voltamos então para nossa idéia inicial, então criaremos uma classe útil para que a automatização do toString(), sendo assim, qualquer classe poderá utiliza-la. Saiba que não iremos ter acesso aos atributos assinados com o modificador Private da classe que deseja utilizar o toString customizado, para resolver tal problema utilizaremos os Métodos públicos, ignorando os métodos da classe Object e aproveitando apenas os métodos acessores.
public final class ToStringUtil{
/*** ocultado ***/
public static String getText(Object object){
StringBuffer buffer = new StringBuffer("");
Method[] methods = object.getClass().getMethods();
for( Method method : methods ){
//
// verifica se o método é um Getter de attributo da classe
//
if( isGetterFieldMethod(method, object.getClass()) ){
buffer.append( getResumeNameMethod( method.getName() ) );
buffer.append(" = ");
buffer.append( getMethodReturnValue(object, method) );
buffer.append( NEW_LINE );
}
}// for each end
return buffer.toString();
}
/*** ocultado ***/
}
Esta versão ainda continua com o problema de tipos complexos, assim que eu for melhorando vou disponibilizar por aqui.
Download
Versão completa ToStringUtil 1.0.1.
Referência
Reflection
Lendo seus e-mails com Java
Lembro de um camarada me dizendo: “Fiz um browser em Java com menos de 20 linhas de código” – Acredite, era verdade! Okay! Okay! Sem recurso nenhum básicamente, apenas a barra de endereços, mas e daí? Assim como esse meu amigo, provavelmente você já tem aquela classe para envio de e-mails guardada na manga, muito útil naqueles dias em que seu chefe pressiona: “É pra ontem!”
Mas… você já tentou montar seu cliente de email? “Pra quê, cara? Eu uso o Gmail” – você irônicamente me responderia, e na tréplica eu diria: Ué! Pra quê o Gmail, se você pode fazer melhor meu rapaz! (sarcástico, não?) Mãos a obra!
O pacote javax.mail.* fornece uma série de recursos para protocolos de internet, utilizando-o como uma receita de bolo o sucesso é garantido. Basicamente vamos criar um programa que monitore a caixa de entrada de um determinado endereço POP3.
Coloque os dados de acesso ao mail em um arquivo config.properties, assim:
pop.hst=pop.lucianosilva.com.br pop.usr=boss@lucianosilva.com.br pop.pwd=ABC123#XYZ
A classe abaixo se encarregará de obter estes dados de conexão:
public class PropertiesMail extends Properties{
private static final String ARQUIVO_CONFIGURACAO = "C:\\config.properties";
public PropertiesMail(){
try{
FileInputStream file = new FileInputStream( ARQUIVO_CONFIGURACAO);
this.load( file );
}catch(IOException e){
e.printStackTrace();
}
}
public String getHost(){
return this.getProperty("pop.hst");
}
public String getAccountUser(){
return this.getProperty("pop.usr");
}
public String getAccountPassword(){
return this.getProperty("pop.pwd");
}
}
Agora, a caixa de correios. Veja que o processo abaixo básicamente segue um roteiro, mas é óbvio que isto é feito apenas para a leitura.
public class Inbox{
public Inbox(){
try {
init();
} catch (MessagingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
private void init() throws MessagingException, IOException{
// Instância das Propriedades de Conexão
PropertiesMail ppm = new PropertiesMail();
// Seta o endereço do host
Properties pp = System.getProperties();
pp.put("mail.pop3.host", ppm.getHost());
// Se o servidor exigir autenticação segura
Authenticator auth = new PopAuthentication();
Session session = Session.getDefaultInstance(pp, auth);
Store store = session.getStore("pop3");
// Dados básicos de conexão
store.connect(ppm.getHost(), ppm.getAccountUser(), ppm.getAccountPassword());
// Obtem o diretório (INBOX)
Folder folder = store.getFolder("INBOX");
folder.open(Folder.READ_ONLY);
// ler as mensagens do diretório
Message message[] = folder.getMessages();
processMessage(message);
// Close connection
folder.close(false);
store.close();
}
private void processMessage( Message[] message ) throws MessagingException, IOException{
if( message == null || message.length==0 ){
System.out.println("Nenhuma mensagem encontrada");
}else{
for (int i=0, n=message.length; i
System.out.println(i + ": " + message[i].getFrom()[0] + "\t" + message[i].getSubject());
String content = message[i].getContent().toString();
System.out.print(content);
}// for end
}
}
public static void main(String[] args){
Inbox inbox = new Inbox();
}
}
Muito fácil não? Mas este é um exemplo simples mesmo, saiba que é possível fazer todo o controle de um cliente de e-mail não só Ler, como Escrever, Apagar, Mover, etc.
Downloads
Projeto utilizado para este post.
JavaMail API
Veja mais sobre JavaMail
JavaMail API
jGuru: Fundamentals of the JavaMail API
WebMail in Java: Reading E-mail
Have a fun!