5 Passos para Exibir Anexos no Siebel CRM sem Baixar para o Client
Neilson Faria
Siebel - Exibir Anexos Sem Baixar
Um cliente solicitou que avaliássemos a possibilidade de exibir anexos do Siebel sem fazer download do arquivo automaticamente para a máquina do usuário, ou seja, sem baixar para o cliente.
O objetivo era adequar o CRM a LGPD no que diz respeito aos prazos legais de retenção de documentos.
Escrever esse artigo certamente consumiu mais tempo que desenvolver a POC. Nós não precisamos criar nenhuma complexidade desnecessária.
Como sempre fazemos, antes de mais nada, vamos alinhar algumas expectativas, ok?
Objetivos e Premissas
O objetivo aqui é apresentar os conceitos e os truques utilizados para resolver esse problema real.
Na imagem abaixo damos uma visão geral da estratégia que elucidamos ao longo desse artigo utilizando como base um ambiente simples com 2 Siebel Servers e 3 servidores de AI que poderiam compartilhar os mesmos recursos computacionais sem problema algum.
Essa POC usa HTML puro e não é um versão recomendada para ambientes de missão crítica. Se sua empresa estiver avaliando uma solução robusta que endereça questões de segurança, recomendo que veja o plugin Siebel Attachment Preview.
Trocando em miúdos, nossa preocupação maior não foi impedir que o usuário pudesse de alguma forma ter acesso ao anexo. Ele poderia exibir o anexo inspecionando o Browser ou fazendo download do arquivo através do próprio plugin que renderiza o conteúdo, como, p. ex, o Adobe Reader, mesmo sem baixar.
Para resolver isso (e nós resolvemos!), precisaríamos fazer uso de algum um JS ou Java filter service específico para exibir anexos o arquivo sem expor a URL do mesmo. Daí, poderíamos adicionar os css no Manifesto ou no próprio código abaixo. Nós não fizemos isso nessa POC, beleza?
O que fizemos até aqui produz o seguinte resultado:
Acreditamos que os conceitos adotados são mais importantes que essa implementação em si. A ideia é que os conceitos sejam aproveitados para que você faça do seu jeito, melhor do que fizemos até agora.
Preferências de Sistemas
Antes de mais nada, verifique se o ambiente tem as Preferências de Sistema abaixo configuradas; Runtime Scripts System Access (default é false) – Precisa ser True se o e-script que você pretende fazer não for de repositório Compiled Scripts System Access (default is true) – O default já atende
Novos diretórios
Você vai precisar criar um novo subdiretório em algum filesystem compartilhado entre os Application Interfaces para armazenar os arquivos SAF descompactados.
Tem que ser num filesystem compartilhado entre os Application Interfaces containers porque todos eles precisam ser capazes de acessar os mesmos arquivos. Ex:
mkdir -p /opt/siebel/fs/downloaded
Também é necessário criar um diretório em algum public path dos Application Interfaces containers.
Nós escolhemos o SIEBEL_AI/applicationcontainer_external/siebelwebroot/files/custom para esse fim e criamos um diretório também chamado downloaded (poderia ser outro nome qualquer, mas pra quê complicar?)
Criar Mount Point do novo diretório no filesystem compatilhado para o novo diretório no public path de cada um dos AI containers.
É necessário permissão superior para editar o fstab, então, ajoelha no milho aí com teu camarada de infra.
vim /etc/fstab
/opt/siebel/ai/applicationcontainer_external/siebelwebroot/files/custom/downloaded /opt/siebel/fs/downloaded none defaults,bind,nofail,x-systemd.device-timeout=2 0 0
Na linha acima, basicamente temos o path de destino, o path de origem os parâmetros de montagem.
Com essa configuração, qualquer arquivo colocado no filesystem compartilhado é acessível pelo public path do AI e vice-versa.
Obs.: Um symlink (comando ln) também funcionaria praticamente do mesmo jeito, mas teríamos que editar arquivos de configuração do app Siebel ou do Catalina e esses arquivos poderiam ser sobrescritos a cada update do Siebel.
Com um Mount Point não precisamos mexer em configuração nenhum Tomcat porque fica tudo transparente para aplicação.
Essa configuração não é necessária se você tiver um mecanismo de proteção dos anexos mais robusto como o que desenvolvemos para o nosso plugin Sebel Attachment Preview.
Shell Script para Mover Arquivos
Crie, em cada um dos servidores de aplicação, um shell script com os seguintes comandos:
vim ~/scripts/move.sh
#!/bin/bash
find "$3" -type f -mmin +30 -exec rm {} \;
mv "$1" "$2"
O primeiro comando do shell move.sh vai apagar qualquer arquivo atualizado a mais de 30 minutos que seja encontrado no diretório passado no parâmetro $3 .
Ele serve para mantermos o novo diretório do filesystem compartilhado sempre com um volume baixo de arquivos.
É importante ressaltar que já guardamos os arquivos compactados no Siebel Filesystem com a extensão .SAF e não vamos mexer nesses arquivos de forma alguma. Os arquivos que vamos apagar são os descompactados que nos servem apenas no momento que estamos o exibindo no browser.
O segundo comando server para mover o arquivo descompactado do SIEBEL_HOME/siebsrvr/temp para o novo diretório compartilhado.
Ah… Vamos chamar esse shell, passando os parâmetros, de dentro do nosso e-Script.
Objetos do Repositório
Aqui, em linhas gerais, os objetos de repositório básicos que você vai precisar:
1 – Um BC de anexo
Crie um campo calculado com a função GetProfileAttr(AttHtml”). Esse campo vai armazenar o HTML dinâmico que vamos gerar dependendo da extensão do anexo que queremos exibir.
2 – Um List Applet de Anexos normal
3 – Um Form Applet normal
Com apenas o Control associado ao campo calculado criado mais acima adicionado no seu WebTemplate.
O Control do Form Applet que contêm a string HTML que vai renderizar o arquivo no browser tem que ter:
O “HTML Type” property = “PlainText”
Um control user property com Name = Encode e Value = FALSE, exatamente como abaixo.
Dessa forma, qualquer marcação, formatação css, ou código js será interpretado como tal pelo aplicação e, consequentemente, pelo browser.
Exemplificando, se o campo tiver o valor “<b>Teste</b>”, com a configuração acima, será exibido Teste, com as marcações html interpretadas e aplicadas.
4 – Uma view com template View 50×50 e uma Screen
Para mapear o list e o form applet de anexos.
5 – Um Server Script no BusComp_ChangeRecord do seu BC de anexo
function BusComp_ChangeRecord ()
{
try
{
TheApplication().SetProfileAttr("AttHtml","");
var vTmshtFileName = this.GetFieldValue("TmshtFileName");
//Parâmetros (Poderia pegar de SysPref? Claro!)
var vUrl="https://crm-tsit.ddns.net/siebel/files/custom/downloaded/"
//Pra onde vamos copiar os arquivos descompactados no Siebel Server
//Lembre-se que o path abaixo é montado no:
//SIEBEL_AI/applicationcontainer_external/siebelwebroot/files/custom/downloaded
var vPath="/opt/siebel/fs/downloaded/";
//Usamos para delimitar quais as extensões fazem sentido serem renderizadas.
//Não faz sentido fazer todo o esfoço do script para, no final, o Browser não
//ser capaz de exibir o anexo. É o caso de arquivos zip, eml, ppt...
var vExtToShow = "pdf|jpg|jpeg|gif|png|bmp|svg|txt|html|htm|eml|mp4|mpeg|ogg";
//HTML padrão. Exibido para quaquer extensão que não seja possível renderizar
var vHtml="
Visualização não disponível
";
//CSS padrão. Exibido para todos.
//Poderia estar no nosso css CUSTOM, mas queria mostrar que é possível, além de css, inserir JS aqui.
var vCss = "\n";
var vExt = "";
var vTemp = "";
var vStatus = "";
var vTempFilePath = "";
var vFileName = "";
var vNewFile = "";
var vCmd = "";
//Inicialmente nós executávamos os comandos que agora estão no shell script move.sh direto do e-Script
//Mas percebemos que existem alguns bugs relacionados a caracteres especiais que impedem o Clib.system
//de funcionar adequadamente.
//a forma que encontramos de resolver isso é restringindo as execuções do Clib.system a comandos simples
//que basicamente chamam o Shell Script e passam parâmetros
// var vCmd="find '" + vPath + "' -type f -mmin +30 -exec rm {} \\;"; //Apaga qqr coisa com mais de 30 minutos
//Se tem Anexo
if (vTmshtFileName.length>0)
{
vExt=this.GetFieldValue("TmshtFileExt");
//Se deve Exibir Anexos
if (vExtToShow.indexOf(vExt)>=0)
{
vTemp = this.InvokeMethod("GetFile", "TmshtFileName");
vStatus = vTemp.substring(0, vTemp.indexOf(","));
//Se conseguiu descompactar o arquivo com sucesso no /temp/
if (vStatus=="Success")
{
//Path do Arquivo Descompactado
vTempFilePath= vTemp.substring(vTemp.indexOf(",") + 1);
//Nome do Arquivo Descompactado
vFileName = vTempFilePath.substring(vTemp.indexOf("/temp/") + 1);
vTemp = vFileName.split("_");
//Remove o nome real do arquivo do nome do arquivo no filesystem (evita erros de caracteres especiais na URL)
vFileName = vTemp[0]+"_"+vTemp[1]+"_"+vTemp[2]+"_"+vTemp[3]+"."+vExt;
vNewFile=vPath+vFileName;
vUrl=vUrl+vFileName;
vTemp = vTempFilePath.split("_");
//Feito script externo ~/scripts/move.sh porque se o arquivo tiver carecter especial no nome, o comando passado via Clib.system falha
//Conteúdo do script ~/scripts/move.sh
// #!/bin/bash
// find "$3" -type f -mmin +30 -exec rm {} \;
// mv "$1" "$2"
vCmd = "/home/oracle/scripts/move.sh " + vTemp[0] + "_" + vTemp[1] + "_" + vTemp[2] + "_" + vTemp[3] + "*." + vExt + " " + vNewFile + " " + vPath;
Clib.system(vCmd);
//Aqui customizamos o HTML dependendo do conteúdo que será renderizado no Browser
if (vExt=="pdf")
vHtml="";
else if (vExt=="jpg" || vExt=="jpeg" || vExt=="gif" || vExt=="png" || vExt=="bmp" || vExt=="svg")
vHtml="
Visualização não disponível
";
else if (vExt=="txt")
vHtml="
Visualização não disponível
";
else if (vExt=="html" || vExt=="htm")
vHtml="
Visualização não disponível
";
else if (vExt=="mp4" || vExt=="mpeg" || vExt=="ogg")
vHtml="
";
}//Conseguiu descompactar o arquivo
}//Deve Mostrar o Arquivo
//O Field é um Campo Calculado com GetProfileAttr("AttHtml");
//Por isso, atualizamos o Attr com o valor
TheApplication().SetProfileAttr("AttHtml",vCss+vHtml);
//Refresh no BC sem Requery
this.InvokeMethod("RefreshRecord");
}//Tem Anexo
}
catch(e)
{
throw e;
}
finally
{
vTmshtFileName = vUrl = vPath = vExtToShow = vHtml = vCss = vExt = vTemp = vStatus = vTempFilePath = vFileName = vNewFile = vCmd = null;
}
}
Tomamos o cuidado de deixar o código bastante comentado e acredito que tenha ficado simples e auto elucidativo.
Uma das características de colocar o código no Change Record do BC é que, basta o usuário selecionar um registro para exibir anexos.
Por isso, podemos, sem o menor pudor, apagar os arquivos descompactados de forma cíclica e frequente porque isso não irá impactar o usuário de forma alguma.
Para os usuários exibindo um anexo que apagamos, enquanto eles estiverem com o registro do anexo ponteirado, o conteúdo dos mesmos continuam sendo exibidos por causa do cache do browser.
Ao saírem dos registros, quando retornarem, outra cópia dos mesmos arquivos serão exibidas para os usuários.
A evolução do código acima, naturalmente, é o desacoplamento do mesmo num Business Service. Dessa forma, podemos utilizá-lo para qualquer Business Component de anexo sem precisar ficar replicando códigos.
Do mais com um BS podemos, ao invés de exibir anexos (o conteúdo do anexo) ao navegar para o registro, só exibi-lo quando o usuário explicitamente solicitar através do clique de um botão ou, como no exemplo abaixo, quando fizer Drilldown para o anexo, o que nativamente iria desencadear o download do arquivo para o Browser. Veja:
Abaixo os Scripts adaptado para usar um Business Service chamado no evento Drilldown de um applet de anexos, como no vídeo acima.
No server script do applet colocar:
function WebApplet_PreInvokeMethod (MethodName)
{
var vReturn = ContinueOperation;
try
{
var vBS = TheApplication().GetService("TSIT Att Operations");
var Inputs = TheApplication().NewPropertySet();
var Outputs = TheApplication().NewPropertySet();
if(MethodName=="Drilldown")
{
TheApplication().SetProfileAttr("AttHtml","");
Inputs.SetProperty("BC",this.BusComp().Name());
Inputs.SetProperty("FieldName","TmshtFileName");
Inputs.SetProperty("FieldExt","TmshtFileExt");
vBS.InvokeMethod("ShowAtt",Inputs,Outputs);
TheApplication().SetProfileAttr("AttHtml",Outputs.GetProperty("HTML"));
this.BusComp().InvokeMethod("RefreshRecord");
vReturn = CancelOperation;
}
}
catch(e)
{
throw (e);
}
finally
{
vBS = Inputs = Outputs = null;
}
return (vReturn);
}
No ChangeRecord do Business Component de anexos colocar:
function BusComp_ChangeRecord ()
{
TheApplication().SetProfileAttr("AttHtml","
Visualização não disponível
");
}
E, finalmente, no Business Service colocar:
function Service_PreInvokeMethod (MethodName, Inputs, Outputs)
{
var vReturn = ContinueOperation;
try
{
if(MethodName=="ShowAtt")
{
vReturn = CancelOperation;
ShowAtt(Inputs, Outputs);
}
}
catch (e)
{
throw e;
}
return (vReturn);
}
function ShowAtt(Inputs,Outputs)
{
try
{
var vBC = Inputs.GetProperty("BC");
var vFieldName = Inputs.GetProperty("FieldName");
var vFieldExt = Inputs.GetProperty("FieldExt");
var vFileName = TheApplication().ActiveBusObject().GetBusComp(vBC).GetFieldValue(vFieldName);
var vUrl="https://crm-tsit.ddns.net/siebel/files/custom/downloaded/"
var vPath="/opt/siebel/fs/downloaded/";
var vExtToShow = "pdf|jpg|jpeg|gif|png|bmp|svg|txt|html|htm|eml|mp4|mpeg|ogg";
var vHtml="
Visualização não disponível
";
var vCss = "\n";
var vExt = "";
var vTemp = "";
var vStatus = "";
var vTempFilePath = "";
var vRealFileName = "";
var vNewFile = "";
var vCmd = "";
if (vFileName.length>0)
{
vExt = TheApplication().ActiveBusObject().GetBusComp(vBC).GetFieldValue(vFieldExt);
if (vExtToShow.indexOf(vExt)>=0)
{
vTemp = TheApplication().ActiveBusObject().GetBusComp(vBC).InvokeMethod("GetFile", vFieldName);
vStatus = vTemp.substring(0, vTemp.indexOf(","));
if (vStatus=="Success")
{
vTempFilePath= vTemp.substring(vTemp.indexOf(",") + 1);
vRealFileName = vTempFilePath.substring(vTemp.indexOf("/temp/") + 1);
vTemp = vRealFileName.split("_");
vRealFileName = vTemp[0]+"_"+vTemp[1]+"_"+vTemp[2]+"_"+vTemp[3]+"."+vExt;
vNewFile=vPath+vRealFileName;
vUrl=vUrl+vRealFileName;
vTemp = vTempFilePath.split("_");
vCmd = "/home/oracle/scripts/move.sh " + vTemp[0] + "_" + vTemp[1] + "_" + vTemp[2] + "_" + vTemp[3] + "*." + vExt + " " + vNewFile + " " + vPath;
Clib.system(vCmd);
if (vExt=="pdf")
vHtml="";
else if (vExt=="jpg" || vExt=="jpeg" || vExt=="gif" || vExt=="png" || vExt=="bmp" || vExt=="svg")
vHtml="
Visualização não disponível
";
else if (vExt=="txt")
vHtml="
Visualização não disponível
";
else if (vExt=="html" || vExt=="htm" || vExt=="eml")
vHtml="
Visualização não disponível
";
else if (vExt=="mp4" || vExt=="mpeg" || vExt=="ogg")
vHtml="
E assim concluímos a POC onde os ponto chave foram:
Uso de Mount Point para disponibilizar arquivos do Siebel Server no Siebel AI
Correto ajuste do Control do Applet para renderizar HTML, CSS e JS dinamicamente em Form Applets (nunca testei em List Applet, mas acredito que não funcione)
Uso do Clib.system para executar comandos no shell (Linux) ou batch (Windows)