segunda-feira, dezembro 27, 2004

Cabeçalhos do HttpServletResponse

Precisavamos enviar um arquivo para o cliente e que ele tivesse a opção de Salvar e não deixar abrir direto no navegador.

Com a ajuda de uma amigo (Ricardo, valeu), foi encontrado os parametros que podem ser passados para o cabeçalho na resposta para o navegador...

Algum tempo atrás tinha colocado aqui como é feito para passar um nome do arquivo para aparecer na tela de Download, agora segue mais informações:

disposition := "Content-Disposition"
":" disposition-type
*(";" disposition-parm)

disposition-type := "inline"
/ "attachment"
/ extension-token
; values are not case-sensitive

disposition-parm := filename-parm
/ creation-date-parm
/ modification-date-parm
/ read-date-parm
/ size-parm
/ parameter

filename-parm := "filename" "=" value

creation-date-parm := "creation-date" "=" quoted-date-time

modification-date-parm := "modification-date" "=" quoted-date-time

read-date-parm := "read-date" "=" quoted-date-time

size-parm := "size" "=" 1*DIGIT


Você pode colocar os disposition-parm todos, lembrando apenas de separá-los por ponto e vigula.

Como isto ficaria no fonte java???
Assim:
response.setHeader("Content-disposition", 

"attachment;filename=" + fileName
+ "creation-date=" + new Date() );

quinta-feira, dezembro 16, 2004

Debuggar aplicações WEB remotamente com Eclipse e Tomcat

O Servidor Tomcat possibilita que você faça debug das aplicações sem precisar rodar localmente, para isto é necessário habilitar o JPDA.

Você sabe o que é JPDA?

JPDA (Java Plataform Debugger Architecture) prove uma infra-estrutura necessária para você criar um debugger para aplicações J2SE.

Ela inclui 3 níveis de API:
  • - Java Debug Interface (JBI), interface da linguagem de alto nível para programação, incluindo suporte para debugging remoto.
  • - Java Debug Wire Protocol (JDWP), o qual define o formato das informações e requisições transferidas entre o processo de debugging e a tela do debugger.
  • - A JVM Tools Interface (JVM TI). Esta que é a interface nativa que define os serviços que a JVM fornece para as ferramentas, incluindo o debugging.
Mas o nosso foco é no debugging remoto, e como as aplicações se comunicarão?

Quando a JVM é configurada para rodar com o JPDA, ela cria um servidor que fica escutando em um "port" definido em sua inicialização.
O Eclipse lhe possibilita criar uma chamada a este servidor para trocar informações entre as JVM's e assim conseguir debugar uma aplicação utilizando a fonte em sua máquina e a aplicação rodando no servidor.

Mas como configurar o servidor?

Estas configurações funcionam perfeitamente no Tomcat 4.1 e no 5.0, para isto é necessário configurar o Tomcat para rodar em Debug mode. É necessário informar alguns parâmetros para a execução do Tomcat, estes parâmetros são:

transport - que pode ter o valor "dt_socket" ou "dt_shmem", onde:
"dt_socket" tem as chamadas através de socket
"dt_shmem" é feito por compartilhamento de memória e funciona apenas no Windows.

address - onde é informado o valor do "port" para comunicação


Existem duas formas de informá-las:

1 - Procurando no arquivo TOMCAT_HOME/bin/catalina.bat no caso do windows e

TOMCAT_HOME/bin/catalina.sh no linux e trocando para os valores:

transport=dt_socket
address=5000 (Definido o "port" para 5000, mas pode ser colocado em qualquer um que não haja conflito)

2 - Colocando como variáveis de ambiente do sistema operacional, sendo definido o tipo e seu valor:

No Windows 2000:
  • a - Clique com o botão direito no ícone "Meu Computador"
  • b - Clique na aba "Avançado" e no botão "Variáveis de Ambiente"
  • c - Clique em novo e na janela que se abre, coloque:
    - Tipo: JPDA_TRANSPORT
    - Valor: dt_socket
  • d - Clique em Ok.
  • e - Repita o passo C com os valores:
    - Tipo: JPDA_ADDRESS
    - Valor: 5000
  • f - Feche todas as janelas clicando em OK.

No Linux:
  • a - Edite o arquivo dentro do seu HOME, .bashrc e inclua as duas linhas
    export JPDA_TRANSPORT=dt_socket
    export JPDA_ADDRESS=5000
  • b - Salve, feche e abra novamente a janela do terminal.


Agora precisa fazer com que o Tomcat rode em debug mode, para isto eu achei mais simples ir até o arquivo TOMCAT_HOME/bin/catalina.bat no caso do windows e TOMCAT_HOME/bin/catalina.sh no linux, procurar a linha que contém:
JPDA=


e trocá-la para:
JPDA=jpda


Depois rodar o servidor normalmente utilizando o arquivo de lote ou batch:
startup.bat/startup.sh

Você pode verificar com o comando netstat -a (no caso do Windows) para saber se o porto está aberto.

Agora como acesso com o Eclipse?

Para acessar o servidor JPDA no Eclipse, basta ir ao menu Run->Debug...

Selecione o item de Configuração "Remote Java Application".

Clique no botão "New"

Na aba "Connect", selecione o projeto onde tem as fontes no campo "Project"
No campo "Connection Type" mantenha "Standard (Socket Attach)"
Em "Connection Properties" coloque em "Host" o nome do servidor onde está o Tomcat e em "Port" o valor colocado na variável ADDRESS.

Depois clique na aba "Source" e selecione a pasta onde tem suas fontes, pesquisando dentro da pasta Default. é importante que esteja selecionado a pasta do projeto onde tem as fontes, pois se manter na pasta "Default" não será encontrado seus fontes.

Por fim, clique em "Debug" e veja na sua perspectiva de Debug do Eclipse que o serviço está rodando.
Coloque um Breakpoint em sua fonte e faça uma chamada, assim você verá as requisições passar pela sua máquina antes de retornar a requisição ao cliente.

:D

As configurações aqui explicadas são para o servidor Tomcat, é possível configurar para demais servidores, para isto eu aconselho a procurar como fazê-lo para seu servidor.
As informações aqui colocadas foram tiradas de pesquisa na Internet, do site da SUN e do que foi necessário fazer para que o processo funcionasse.

Alguns pontos ao qual seria boa alguma pesquisa:
- Como fazer para rodar o servidor em modo de debug como serviço do Windows?

Espero que vocês possam tirar bons proveitos

Para mais informações:
http://java.sun.com/products/jpda/

Nome de arquivo em download

Uma coisa que precisei fazer foi...

Ao requisitar um arquivo que só de ser acessado em memória, fiz um Servlet que le este arquivo da memória e envia para o cliente.

O problema era que no nome que aparece para fazer download era o nome do Servlet e não do arquivo ao qual era requisitado.

Para resolver este problema, existe um atributo que pode ser passado no Header do objeto javax.servlet.http.HttpServletResponse:
response.setHeader("Content-disposition", "filename=" + fileName);

:)

Assinatura de textos com JAVA

Venho a algum tempo utilizando algoritmos para assinatura de textos. E com aprendi algumas coisas que acho importante colocar aqui.

Vamos colocar alguns termos aqui:

JCE (Java Cryptographic Extension) - Interface para adicionar extensões de providers que fornecem recursos de criptografia, assinatura digital, entre outros recursos de segurança

Provider - Empresa responsável por fornecer implementações para as interfaces do JCE criado pela SUN.

PKCS#7 - Padrão criado pela RSA Security para comportar conteúdo de textos assinados, certificados e a assinatura. Veja: RSA Security

O provider da SUN para JCE fornece uma interface e métodos simples para assinatura de texto. Vejamos um exemplo rápido para fazer a assinatura:
public byte[] assinar(PrivateKey chavePrivada, 
byte[] textoParaAssinar) {
Signature signer = Signature.getIntance("SHA1withDSA");
signer.initSign(chavePrivada);
signer.update(textoParaAssinar);
byte[] dadosAssinador = signer.sign();
return dadosAssinador;
}

O problema deste tipo de assinatura é que apenas o hash é gerado e assim a assinatura não fica num padrão com o recomendado pela RSA (PKCS#7).

Para resolver este problema existe um provider que fornece um modo diferente, mas utilizando as mesma chamada internamente para fazer a assinatura no formato PKCS#7.

Este provider é o Bouncy Castle, ele contém recursos que não são fornecidos pela SUN, é livre e pode ser utilizado comercialmente.

Para a utilização de assinatura com este novo provider, o código fica um pouco diferente, veja:
public byte[] assinar(PrivateKey chavePrivada, 
byte[] textoParaAssinar,
Certificate[] certificadosArray,
CRL[] crlsArray) {
Security.addProvider(new BouncyCastleProvider());

PKCS7SignedData signer = new PKCS7SignedData(chavePrivada,
certificadosArray,
crlsArray, "SHA1", "BC");
signer.update(textoParaAssinar, 0, textoParaAssinar.length);
byte[] dataSigned = signer.getEncoded();
return dataSigned;
}

Este processo gera um array de byte com a estrutura indicada pelo padrão PKCS#7, contendo os certificados, a lista de CRL, informações do assinador (SignerInfos) e o hash gerado.

Mas existe um problema...

Não é adicionado o conteúdo (textoParaAssinar) dentro do pacote da assinatura. Assim o texto que foi assinado fica de fora da estrutura e para conferir se a estrutura é válida, é necessário fornecer o texto junto com a assinatura.

Como este não é um modo ideal para enviar a assinatura apenas... Procurei o motivo de não haver o contúdo dentro da assinatura.

Então descobri que o provider Bouncy Castle ainda não implementa o conteúdo interno. Assim deixando ele em branco, veja: (extraído do fonte)

// Create the contentInfo. Empty, I didn't implement this bit
//
DERSequence contentinfo =
new DERSequence(new DERObjectIdentifier(ID_PKCS7_DATA));

Então para resolver este problema, decidi adicionar algumas linhas para incluir e recuperar o conteúdo do texto assinado.

Como o código da classe é um pouco grande (também não faz sentido colocar ele todo aqui), vou colocar apenas as parte que alterei.

Um resultado semelhante, encontrei nos arquivos da lista de discursão de desenvolvedores do Bouncy Castle. Mas pelo que vi, apenas recupera o conteúdo de um arquivo no formato PKCS#7 já existente. Ele não cria um com conteúdo.

Bom... Vamos aos códigos.

Todas as alterações foram feitas no arquivo org.bouncycastle.jce.PKCS7SignedData.java.

E para saber onde está inserido o código, coloque o comentário antes da nova entrada (// XXX Implementado por Dadario).

A primeira coisa foi adicionar uma variável global para armazenar o conteúdo, utilizei um java.util.List, mas estou pensando em trocar por armazenamento com Objetos do próprio Bouncy Castle.
***
private final String ID_DSA = "1.2.840.10040.4.1";

// XXX Implementado por Dadario
private List content = new ArrayList();

/**
* Read an existing PKCS#7 object from a DER encoded byte array using the BC
* provider.
*/
public PKCS7SignedData(byte[] in)
throws SecurityException, CRLException, InvalidKeyException,

CertificateException, NoSuchProviderException, NoSuchAlgorithmException {
***

Na leitura dos bytes do PKCS#7 foi colocado para extrair o conteúdo adicionar na variável de conteúdo
***
public PKCS7SignedData(byte[] in, String provider)
throws SecurityException, CRLException, InvalidKeyException,

CertificateException, NoSuchProviderException, NoSuchAlgorithmException {
DERInputStream din = new DERInputStream(new ByteArrayInputStream(in));

//
// Basic checks to make sure it's a PKCS#7 SignedData Object
//
DERObject pkcs;

try {
pkcs = din.readObject();
} catch (IOException e) {
throw new SecurityException("can't decode PKCS7SignedData object");
}

if (!(pkcs instanceof ASN1Sequence)) {
throw new SecurityException("Not a valid PKCS#7 object - not a sequence");
}

ContentInfo content = ContentInfo.getInstance(pkcs);

// XXX Implementado por Dadario
DEREncodable dataContent = content.getContent();
byte[] conteudo = extractContent(dataContent);
if(conteudo != null) {
for (int i = 0; i < conteudo.length; i++) {
this.content.add(new Byte(conteudo[i]));
}
}
if (!content.getContentType().equals(signedData)) {
throw new SecurityException("Not a valid PKCS#7 signed-data object"
+ " - wrong header "
+ content.getContentType().getId());
}
***

Quando recuperado os bytes com o formato PKCS#7, não era adicionado o conteúdo. Neste ponto fiz a alteração para que o conteúdo também fosse incluído:
***
public byte[] getEncoded() {
try {

digest = sig.sign();

// Create the set of Hash algorithms. I've assumed this is the
// set of all hash agorithms used to created the digest in the
// "signerInfo" structure. I may be wrong.
//
ASN1EncodableVector v = new ASN1EncodableVector();
for (Iterator i = digestalgos.iterator(); i.hasNext();) {
AlgorithmIdentifier a = new AlgorithmIdentifier(
new DERObjectIdentifier((String) i.next()), null);
v.add(a);
}

DERSet algos = new DERSet(v);

// Create the contentInfo. Empty, I didn't implement this bit

// XXX Implementado por Dadario
v = new ASN1EncodableVector();
v.add(new DERObjectIdentifier(ID_PKCS7_DATA));

DERTaggedObject contentObject = new DERTaggedObject(
true, 0, new DEROctetString(getContent()));

v.add(contentObject);

DERSequence contentinfo = new DERSequence(v);

// Chamada antiga do BouncyCastle
// DERSequence contentinfo =
new DERSequence(new DERObjectIdentifier(ID_PKCS7_DATA));
***

E no final da classe, adicione dois métodos, sendo que achei necessário ser na própria classe e um deles private só para fazer o serviço de extraír o conteúdo.

***
// XXX Implementado por Dadario
/**
* @return The content em byte format
*/
public byte[] getContent() {

byte[] conteudo = new byte[content.size()];

int i = 0;
for (Iterator iter = content.iterator(); iter.hasNext(); i++) {
Byte element = (Byte) iter.next();
conteudo[i] = element.byteValue();
}
return conteudo;
}

// XXX Implementado por Dadario
/**
* @param dataContent
*/
private byte[] extractContent(DEREncodable dataContent) {

if (dataContent instanceof ASN1Sequence) {
ASN1Sequence dataSequence = (ASN1Sequence) dataContent;

for (int i = 0; i < dataSequence.size(); i++) {
DEREncodable objectAt = dataSequence.getObjectAt(i);
byte[] retorno = extractContent(objectAt);
if(retorno != null) {
return retorno;
}
}
}
if(dataContent instanceof DERTaggedObject) {
DERTaggedObject ob = (DERTaggedObject) dataContent;
byte[] retorno = extractContent(ob.getObject());
if(retorno != null) {
return retorno;
}
}
if(dataContent instanceof DEROctetString) {
DEROctetString oc = (DEROctetString) dataContent;
return oc.getOctets();
}
return null;
}

} // Fim da classe

Se quizer o código já pronto do provider com a modificação, basta me pedir por e-mail.

Do mais... Fica minha contribuição de muito trabalho e quebra-cabeça para colocar este conteúdo dentro do padrão.

ATUALIZAÇÃO: Não tenho mais os códigos. Também é preciso verificar uma versão nova do Bouncy Castle para saber se eles já não estão tratando o conteúdo assinado.

JavaScript acessando objetos Java

Desenvolvendo uma aplicação (Applet) que deve ser lançada por um script em JavaScrit e depois de dentro do applet atualizar a página html através do Applet. Tivemos que criar algumas chamadas do JavaScript ao objeto Java.

Para isso foi criado um applet que lança um JOptionPane com os recursos para obter os dados necessários.

O problema era em, como chamar um método do applet pelo JavaScript. Com a ajuda de um amigo, conseguimos resolver o problema.

Para isso, após ter colocado seu applet como objeto na página HTML (Veja a ferramenta HtmlConvert que vem no pacote do J2SDK), o acesso a ele foi feito desta maneira no javascript:
var myApplet = document.myApplet;

myApplet.execute();

Só tome cuidado que se for criado para IE e NS, deve diferenciar os nomes do applet (Sendo object com um nome e embed com outro) e verificar na hora da chamada qual recuperar para executar. (Tive alguns problemas com este recurso).

Para utilizar recursos do javascript no applet, deve ser criado um Objeto JSObject, assim:
JSObject win = JSObject.getWindow(this);

win.eval("javascript:alert('Hello word')");

Para utilização deste objeto deve ser incluido o pacote plugin.jar que consta dentro das bibliotecas do JRE.

Este cógido eu agradeço ao amigo Riko que ajudou a encontrar como fazer isso.

E mais uma coisa que foi descoberto depois...

Correndo atrás de executar um método que eu tinha criado em um arquivo JavaScript. Não adianta só utilizar o método eval(), como mostrado acima, para fazer chamadas a seus métodos, precisa fazer a chamada com o método call, desta forma:
JSObject win = JSObject.getWindow(this);

win.call("meuMetodo", new Object[] {umValor});

Esta chamada faz com que chame o método no meu JavaScript:
function meuMetodo(valorEntrada) {

alert(valorEntrada);
}


O négocio é este. Divirta-se :)

Testes para stored procedures

Começamos a fazer este projetinho, mas paramos por ter um que faz isto é algo mais (http://mockrunner.sourceforge.net/)... Mas:

Você já teve o problema de fazer testes para suas classes e se deparou com o problema de testar as classes que acessam banco de dados?

Bom...
Tivemos este problema num projeto e tocamos a desenvolver uma camada para simular as conexões ao banco de dados para retornar o que fosse preciso.

No momento temos uma release simples para simular a chamada ao banco por stored procedures. (java.sql.CallableStatement)

Mas estamos criando as funcionalidades para simular chamadas de SQL por java.sql.PreparedStatement e java.sql.Statement


Você pode conferir nosso projeto no site:
http://connmother.sourceforge.net

ConnectionMother
Um object mother para JDBC e CallableStatement

quinta-feira, outubro 28, 2004

Abrindo as portas |] |

Tudo tem um começo...

E aqui que começa o meu Blog.
Vou tentar deixar por aqui fontes que possam ajudar em futuros desenvolvimentos e idéias que são criada em debates entre amigos.

Espero ter algo pelo menos 1x por dia... Mas caso não, estarei atualizando sempre que pintar algo interessante.

Vamos ver se dá certo.