Tenho um código desenvolvido para isso que ainda não foi 100% testado. Caso vc usá-lo, please poste o resultado no fórum para avaliação de todos:
Considerações Iniciais
Na maioria das aplicações web, reenviar um formulário não causa maiores transtornos. Porém, em algumas aplicações críticas, financeiras por exemplo, todo o cuidado deve ser tomado para que isto não ocorra. O uso de Javascript ajuda a resolver esse problema no lado do navegador. No lado servidor, Java deve ser chamado para o resgate.
O código apresentado neste tópico é uma tentativa de implementar as idéias de um artigo sobre o assunto na revista Dr. Dobbs.
O código
As servlets da aplicação devem ser derivadas de uma servlet abstrata que contém o seguinte código:
// os métodos a seguir são para a consistência de transações web
/**
* Verifica a consistência de uma transação Web.
* Este método pode ser chamado por uma servlet
* antes de completar uma transação.
*
* @return int com o código da verificação:
* 0: Ok
* 1: flowId não encontrado (transações cruzadas)
* 2: transaction tokens são diferentes (envio repetido de formulário)
* 3: resultado inesperado: flowDict é null (problema nas páginas html?)
* @throws ServletException se um dos parâmetros da transação faltar (flowId ou transTk)
*/
public int checaTransa() throws ServletException
{
String flowId = req.getParameter("flowId");
String transTk1 = req.getParameter("transTk");
if(flowId == null || transTk1 == null)
throw new ServletException("transaction token missing");
Hashtable flowDict = (Hashtable) session.getAttribute("flowDict");
if(flowDict == null)
return 3;
String transTk2 = (String) flowDict.get(flowId);
if(transTk2 == null)
return 1;
if(transTk1.equals(transTk2))
return 0;
return 2;
}
/**
* Gera o identificador único de uma transação web.
* Este método pode ser chamado por um Estado que gere um formulário dinâmico.
* Se for usado diretamente num URL, deve passar antes por URLEncoder.encode()
*
* @param flowId String identificando o fluxo
* @return String codificada em base64 com o transaction token, que deve ser colocado num
* campo hidden do formulário
* @throws ServletException
*/
public String geraTransa(String flowId) throws ServletException
{
// abre o dicionário de fluxo
//
Hashtable flowDict = (Hashtable) session.getAttribute("flowDict");
if(flowDict == null)
{
flowDict = new Hashtable();
session.setAttribute("flowDict", flowDict);
}
//
// gera um novo transaction token
//
String token = null;
try
{
token = Acme.Utils.base64Encode(generateTransactionToken(flowId));
}
catch(Exception e)
{
throw new ServletException(e);
}
//
// atualiza o dicionário
//
flowDict.put(flowId, token);
return token;
}
/**
* Gera o próximo transaction token.
* Ele é o hash de um número aleatório baseado no tempo real.
*
* @param flowId String identificadora do fluxo de acessos
* @return um array de bytes contendo o token
*/
protected byte[] generateTransactionToken(String flowId) throws Exception
{
MessageDigest sha = MessageDigest.getInstance("SHA-1");
sha.update(flowId.getBytes());
sha.update(session.getId().getBytes());
java.util.Date d = new java.util.Date();
long dl = d.getTime();
sha.update((Long.toString(dl)).getBytes());
return (sha.digest());
}
As páginas dinâmicas de formulário que iniciam uma transação devem gerar os tokens de transação:
String flowId = "flow" + Util.getNonce();
String transTk = geraTransa(flowId);
//
setTemplate("principal.html");
synchronized (pagina)
{
pagina.setPlaceHolder("flowId", flowId);
pagina.setPlaceHolder("transTk", transTk);
pagina.send();
}
As servlets que processam uma etapa da transação (geralmente uma só) devem verificar a validade da transação e agir de acordo:
//
// analisa a transação
//
try
{
int transa = checaTransa();
switch(transa)
{
case 0:
... // Ok, transação válida
break;
case 1:
... // flowId não encontrado (transações cruzadas?)
break;
case 2:
... // transaction tokens são diferentes (envio repetido de formulário?)
break;
case 3:
... //resultado inesperado: flowDict é null (problema nas páginas html?)
break;
}
}
catch(ServletException e)
{
// um dos parâmetros da transação faltando (flowId ou transTk)
}
...
// repassa os transactions para a página dinâmica (caso do Velox)
//
String flowId = req.getParameter("flowId");
String transTk = geraTransa(flowId);
//
pagina.setPlaceHolder("flowId", flowId);
pagina.setPlaceHolder("transTk", transTk);
...
Os formulários que têm que ser rastreados devem receber os tokens de transação para serem repassados para a servlet que processa o post:
<FORM ACTION="post" ...>
<INPUT TYPE="hidden" NAME="flowId" VALUE="$flowId">
<INPUT TYPE="hidden" NAME="transTk" VALUE="$transTk">
...
</FORM>