sábado, 11 de abril de 2009

Upload de Arquivos com Self Service E-Business Suite (OAF)

Oi Pessoal!

Seguindo a série de posts sobre OAF, hoje a dica é sobre upload de arquivos via Self-Service do Oracle E-Business Suite.

Em aplicações baseadas em Forms, a Oracle previu a necessidade de anexar documentos a uma determinada entidade, por exemplo anexar a NF digitalizada ao documento fiscal dentro do RI. Para isso, habilita-se um cadastro de Categorias de Anexo e Entidades, para que seja habilitado o botão de anexar (ícone do Clips) dentro do Forms. Ao habilitar este botão, clicando nele o usuário pode fazer o Upload de um arquivo, que é automaticamente anexo ao documento em questão. Isto é feito internamente nas tabelas FND_LOBS (guarda o conteúdo do arquivo em BLOB), FND_DOCUMENTS (dados do documento) e FND_ATTACHED_DOCUMENTS (Associação do DOCUMENT_ID com o PK1_VALUE que representa a chave primária da entidade origem, por exemplo, INVOICE_ID).

Em aplicações OAF, também é possível ativar essa funcionalidade, mesmo sem fazer o cadastro de Entidade mencionado acima. Por exemplo, podemos adicionar um Link de Anexos (Ícone de Clips) em uma coluna de uma Table ou AdvancedTable. Para isto, basta adicionarmos um item do tipo "AttachmentImage" dentro da tabela, conforme abaixo:



Como vocês podem notar acima, o AttachmentImage tem alguns sub-elementos, a saber:

EntityMap (PlayFuncPKMap): é o que define a entidade a qual o anexo será subordinado. no atributo "Entity" devemos inventar um nome que será usado como referência, por exemplo, PlayFuncPK. Todas as telas OAF que tiverem link de anexos para esta mesma entidade (EO) devem usar essa mesma referência.

PrimaryKeys (PlayFuncPK): define qual a chave primária da entidade que deve ser gravada no campo PK1_VALUE da FND_ATTACHED_DOCUMENTS, para guardar a associação anexo/registro. Informe no campo "ViewAttribute" o atributo do VO que é a chave primária do EO correspondente.

CategoryMap (PlayFuncCatMap): define quais categorias de anexo são permitidas para esta entidade. Informe no campo "Category" o nome interno da categoria criada (campo NAME da tabela FND_DOCUMENT_CATEGORIES). Caso não queira limitar as categorias possíveis, é só não incluir o CategoryMap debaixo do EntityMap que ele libera todas.

Após estes simples passos, a tela ficará conforme abaixo, mostrando um ícone de Clips (para visualizar os anexos existentes) e um ícone de Mais ao lado (para adicionar o anexo):



Ao clicar no ícone de Mais, segue-se para a tela de adicionar anexo (core), podendo adicionar anexos do tipo Link, Texto Curto ou então Arquivos (botao browse):


Ao clicar no ícone de Clips, vemos a tabela de anexos:


Abraços, e até a próxima!

sábado, 4 de abril de 2009

AdvancedTable dentro de AdvancedTable

Bom Dia Pessoal,

Hoje, após um grande período sem posts como o Thiago mesmo falou anteriormente, vamos voltar com alguns assuntos bastante interessante referente a OAF (Oracle Applications Framework).
Para quem não conhece, OAF é a tecnologia que a Oracle utiliza para Customização e Desenvolvimento de Páginas WEB dentro do ERP (eBS).
E como é uma recomendação da própria Oracle a não utilização do Forms nas próximas releases, vamos iniciar uma sessão de posts interessantes para quem quer iniciar ou mesmo os desenvolvedores mais experientes. O Post de hoje é muito interesasnte, pois se trata de uma Página contendo uma AdvancedTable dentro de outra AdvancedTable.
Nosso objetivo é criar uma página onde a visualização fique bastante facilitada, por conta do Máster-Detail entre as tabelas, como a imagem abaixo.


Primeiro Passo: Nesse caso teremos dois ViewObjects (VO) ligados através de um ViewLink (VL). Imaginem uma tabela de Períodos e outra de Trabalhos realizados, onde a ligação entre as mesmas seja o atributo PeriodId, existe vários trabalhos realizados para cada período. Seu ApplicationModule (AM) vai ficar dessa forma:
Segundo Passo: Feito a amarração entre os VO´s, vamos criar a página.
Crie uma página comum Oracle Applications (OA), na mesma você pode criar uma região para que a tabela fique mais organizada. Adicione uma AdvancedTable dentro da Região criada, e atribua suas respectivas colunas referente ao VO de Períodos conforme sua necessidade, feito isso vamos clicar com o botão direto na mesma e habite a opção “detail”.
Dentro do “detail” vamos adicionar outra AdvancedTable que será baseada no VO de trabalhos Realizados. A imagem abaixo irá ilustrar a estrutura da página.


Terceiro Passo: Criar o Controller (CO) da página, onde será criado a amarração entre os VO´s através do VL.
No método do controller “processRequest”, vamos colocar o seguinte código:

OAWebBean outerTable = (OAWebBean)webBean.findChildRecursive("AdvancedTableRN");
OAWebBean innerTable = (OAWebBean)webBean.findChildRecursive("SubAdvancedTableRN");
if (outerTable != null)
{
//temos que setar exatamento o atributo do VO
outerTable.setAttributeValue(CHILD_VIEW_ATTRIBUTE_NAME,"PeriodId");
// temos que setar exatamento o valor do VL que está no AM
outerTable.setAttributeValue(VIEW_LINK_NAME,"PeriodToWorkRealizedVL");
}
if (innerTable != null)
{
//temos que setar exatamento o atributo do VO
innerTable.setAttributeValue(CHILD_VIEW_ATTRIBUTE_NAME,"PeriodId");
// temos que setar exatamento o valor do VL que está no AM
innerTable.setAttributeValue(VIEW_LINK_NAME," PeriodToWorkRealizedVL ");
}

Feito esses 3 passos acima, é só rodar a página e visualizar o resultado.

Esse foi mais um post da série OAF, um grande abraço e boa diversão.

quinta-feira, 2 de abril de 2009

Submeter Concurrent via Self-Service (Após um longo inverno...)

Oi Pessoal!

Sei que faz algum tempo que não postamos aqui no blog, mas tudo tem seu lado bom: coletamos bastante "material" para enriquecê-lo e compartilhar o conhecimento com a comunidade da Tecnologia Oracle.

Para reiniciar com o pé direito, fica uma dica simples e rápida: Sabemos que através de qualquer responsabilidade, podemos acessar a fila de concorrentes e submeter um novo pelo menu "Exibir -> Solicitações". No entanto, o que é importante notar é que existe uma função core que permite que façamos exatamente o mesmo através do Self-Service Web Oracle Applications, sem sequer entrar no Forms.

Para conseguir que o usuário submeta um concurrent pela Web, basta adicionar ao menu da responsabilidade uma função chamada "Submeter Solicitações" (Código FNDCPSRSSSWA) ou então, se o objetivo é somente exibir a fila dos concorrentes submetidos, adicionar a função "Exibir Solicitações" (Código FNDCPVIEWREQUEST). Experimentem!

Até a próxima!

segunda-feira, 17 de novembro de 2008

ADF BC: Como inativar um registro ao invés de deletar

Olá novamente pessoal!

Hoje vamos fazer um rápido HOW-TO baseado em um requisito bastante comum. Por várias vezes, os clientes solicitam sistemas que não permitem a deleção de registros, para fins de histórico. Ao invés disso, cria-se na tabela um campo STATUS, que pode levar um flag ATIVO (por exemplo, 'A') ou INATIVO ('I'). Sabemos que o BC tem uma ação padrão chamada "Delete" que remove o registro do RowSet do View Object e, ao efetuar commit(), emite uma instrução DELETE ao banco de dados. E agora?
Para solucionar este problema, vamos utilizar a orientação a objetos do Java e estender a implementação da Entidade, sobrescrevendo os métodos responsáveis pela remoção e pela execução do DML. Dividiremos essa tarefa em duas partes:

PARTE 1: COMO ALTERAR O STATUS DO REGISTRO AO INVÉS DE REMOVER?
Para alcançar este primeiro objetivo, sobrescrevemos o método remove() da classe de implementação da entidade. Por exemplo, supondo que nossa entidade chame EmployeeEO e que existe um campo chamado STATUS dentro dessa entidade, teremos uma classe chamada EmployeeEOImpl onde iremos criar o seguinte método, sobrescrevendo da superclasse:


public void remove() {
// Chamada nova: setar o valor do status para 'I'
this.setStatus("I");

// Chamada original, comentada
// super.remove();
}


Com isso, ao invés de remover a linha do RowSet, ele irá somente alterar o status para "I". Porém, imaginem que precisemos manter a chamada de super.remove() no método para que, ao inativar o registro, ele suma da tela do usuário. Se isso for verdade, temos de achar alguma maneira de dizer ao BC que não é necessário um DELETE, mas sim um UPDATE. Aí vem a parte 2...

PARTE 2: ALTERANDO A LÓGICA DE DML DO ENTITY OBJECT
Na mesma classe de implementação da entidade, temos um método chamado doDML(int operation, TransactionEvent e). Este método é responsável por disparar os comandos DML no banco de dados, portanto vamos desenvolver um "gancho" que intercepta essa chamada e altera a operação desejada de DELETE para UPDATE, sobrescrevendo o método como abaixo:

public void doDML(int operation, TransactionEvent e) {
//O parâmetro "operation" indica a operação desejada
//Pode ser DML_UPDATE, DML_DELETE, DML_INSERT...
//A nossa ação é verificar se é um DML_DELETE, e se for, trocar para DML_UPDATE
if (operation == DML_DELETE) {
operation = DML_UPDATE;
}

// Ao chamar a superclasse, vai "enganá-la", emitindo um UPDATE!
super.doDML(operation, e);
}


Com isso, você pode usar a operação "Delete" do application module normalmente, que a entidade irá se comportar da maneira desejada. Pode-se inclusive chamar row.remove() em uma linha do View Object programaticamente que o comportamento está garantido.

Bom ADF para vocês! Até Mais!

sábado, 1 de novembro de 2008

ADF BC: Usando History Columns sem JAAS

Olá pessoal!

Neste post vou chamar a atenção para um artifício extremamente útil durante a programação ADF BC. Encontrei esta solução em um artigo neste link abaixo, que depois apliquei e testei em projetos: http://my.opera.com/dominionspy/blog/show.dml/575983

Em resumo: o ADF BC possui um mecanismo muito prático de rastreamento de registro por usuário, que é chamado de History Columns. Imaginem que temos um campo, CREATED_BY, na nossa tabela no qual queremos gravar o nome do usuário que criou o registro. O ADF BC permite que marquemos este campo como History Column ao editarmos o Entity Object e visualizarmos as propriedades do atributo CreatedBy, mapeado para essa coluna. Ao marcarmos este campo como History Column, também selecionamos o tipo de histórico para "Created By" para que automaticamente o BC pegue o usuário logado e grave-o nessa coluna durante a criação, sem nenhuma intervenção do programador. Simples, não?

Essa funcionalidade tem um porém: o BC só consegue pegar o usuário logado nativamente se você estiver usando a segurança declarativa, via WEB.XML (as tags por exemplo) e JAAS. Sabemos, no entanto, que em muitos dos nossos projetos o uso do JAAS é impossível, devido a limitações do framework (por exemplo, o JAAS só nos permite ter os dados de Usuário e Senha, nada mais) ou então de limitações do projeto, por já haver componentes de segurança feitos em outra tecnologia, por exemplo, Http Filters. Felizmente, uma simples mudança pode permitir que utilizemos esse outro método de autenticação sem perder as History Columns.

Funciona assim: o BC, para buscar o usuário logado, utiliza o método getRemoteUser() da Request HTTP, ou seja, o usuário que é populado pelo JAAS ao autenticar. Porém, existe um objeto chamado HttpServletRequestWrapper que permite que "embalemos" uma request original em um objeto customizado e passemos adiante através de um Filtro por exemplo, sobrescrevendo o método getRemoteUser() original e retornando nosso próprio usuário. Para isso, siga os passos adiante, que estão listados inclusive no link citado no início da página:

1) Crie um objeto que implemente a interface java.security.Principal, é o objeto que vai guardar os dados de usuário. Tem dois métodos: getName() e getRole(). Exemplo:

import java.security.Principal;

public class AuthUserPrincipal implements Principal {
private String username;
private int role;

public AuthUserPrincipal(String _username, int _role) {
this.username = _username;
this.role = _role;
}

public String getName() {
return username;
}

public int getRole() {
return role;
}
}


2) Crie uma classe que estenda javax.servlet.http.HttpServletRequestWrapper. Esta classe deve conter um construtor que permita informar o nome de usuário, e sobrescrever os métodos isUserInRole() e getRemoteUser. Exemplo:

import java.security.Principal;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;

public class AuthRequestWrapper extends HttpServletRequestWrapper {
private AuthUserPrincipal principal;

public AuthRequestWrapper(HttpServletRequest request, String usuario, String role)
super(request);
this.principal = new AuthUserPrincipal(usuario, role);
}

public boolean isUserInRole(String string) {
return String.valueOf(principal.getRole()).equals(string);
}

public String getRemoteUser() {
return principal.getName();
}

public Principal getUserPrincipal() {
return principal;
}
}


3) Crie um filtro Http que "embale" o HttpServletRequest que recebe de parâmetro no seu HttpServletRequestWrapper. Por exemplo, supondo que seu filtro de autenticação colocou o nome do usuário e role na sessão Http:

public class BCAuthFilter extends javax.servlet.Filter {
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest)req;
String user = request.getSession().getAttribute("user"); // Usuario
String role = request.getSession().getAttribute("role"); // Role

AuthRequestWrapper wrap = new AuthRequestWrapper(request, user, role);
chain.doFilter(wrap, resp);
}
}


4) Configure este filtro no seu Web.xml. IMPORTANTE: Você deve configurar este filtro antes dos filtros adfBindings e adfFaces, mas DEPOIS do seu filtro de autenticação:


BCAuthFilter
com.custom.BCAuthFilter


BCAuthFilter
/*



Desta maneira o seu aplicativo irá se logar através do filtro de autenticação; em seguida, o seu BCAuthFilter irá sobrescrever esses dados no Request, para que depois o ADFBindingFilter consiga recuperar esse dado em sua inicialização, através do request.getRemoteUser() (que na verdade executará o metodo sobrescrito do seu Wrapper).

Abraços!