Install
$ agentstack add skill-snk-devcenter-addon-studio-before-load-listener ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Interceptador de Buscas (@BeforeLoadListener) — Addon Studio 2.0
@BeforeLoadListener executa lógica personalizada sempre que uma entidade é carregada ou pesquisada via JAPE. Intercepta a execução do Finder — permite adicionar filtros, ordenações ou validar parâmetros de busca antes da query chegar ao banco. A anotação automatiza o atributo finder-listener no XML da entidade — sem edição manual de XML.
> ⚠️ @BeforeLoadListener ≠ @Listener. São mecanismos distintos: @BeforeLoadListener intercepta leitura (busca/carregamento, via FinderListener) e só funciona em instâncias do próprio add-on; @Listener reage a escrita (insert/update/delete, via PersistenceEventAdapter) e aceita também instâncias nativas. Tarefa de gravação/exclusão → skill listener.
1. Quando usar — @BeforeLoadListener vs @Criteria vs @Listener vs @BusinessRule
| Mecanismo | Escopo | Quando usar | |:----------------------|:--------------------------------------------------------|:--------------------------------------------------------------------------------------------------| | @BeforeLoadListener | Toda busca/carregamento de UMA entidade (transversal)| Filtro transversal aplicado a toda leitura da entidade, independente de quem consulta (segurança, multi-tenant, soft-delete). | | @Criteria (repository)| Uma query específica | Filtro pontual em um método de busca declarado no repository. | | @Listener | CRUD (insert/update/delete) de qualquer entidade | Validar/modificar campos ao gravar/excluir. | | @BusinessRule | Confirmação/faturamento de notas (saída, mov. interna) | Regras transacionais comerciais com barramento de regras. |
Regra rápida: filtro que precisa valer em todas as buscas da entidade (sem depender de quem chamou)? @BeforeLoadListener. Filtro de uma consulta só? @Criteria no repository.
2. Pré-requisito fundamental
A entidade precisa estar declarada no Dicionário de Dados — de uma das duas formas:
- Arquivo XML de entidade (ex.:
Produto.xmlemdatadictionary/) — skilldata-dictionary. - Anotação
@JapeEntityna classe de modelo — skillentity.
> Sem declaração por nenhum desses meios, o SDK não tem onde injetar a configuração do listener.
Restrição de instâncias — só as do próprio add-on
@BeforeLoadListener só pode interceptar instâncias criadas pelo próprio add-on. NÃO é permitido em instâncias nativas do sistema (ex.: Parceiro, Produto nativo, CabecalhoNota, etc.).
3. Anatomia
import br.com.sankhya.jape.core.FinderListener;
import br.com.sankhya.jape.metadata.EntityMetaData;
import br.com.sankhya.jape.util.FinderWrapper;
import br.com.sankhya.studio.persistence.BeforeLoadListener;
import lombok.extern.java.Log;
@Log
@BeforeLoadListener(instance = "MinhaInstancia")
public class MinhaInstanciaFinderListener implements FinderListener {
@Override
public void beforeExecute(EntityMetaData entity, FinderWrapper finder) throws Exception {
// Adiciona um criterio de busca — setWhere SUBSTITUI a clausula inteira,
// por isso compomos com o filtro ja existente via getWhere()
String where = finder.getWhere();
finder.setWhere(where == null || where.isEmpty()
? "this.ATIVO = 'S'"
: where + " AND this.ATIVO = 'S'");
log.info("Filtro de seguranca aplicado para a entidade: " + entity.getName());
}
}
> Atenção: setWhere(String) substitui a cláusula WHERE por completo. Para adicionar um critério sem descartar o filtro que já veio na consulta, sempre componha com getWhere() + " AND ...", como acima.
4. Atributo da anotação @BeforeLoadListener
| Atributo | Obrigatório | Descrição | |:-----------|:------------|:-------------------------------------------------------------------------------------------| | instance | Sim | Nome da instância (entidade) a interceptar — o mesmo valor de @JapeEntity(entity = "...") ou de `` no XML do dicionário. Não é o nome da tabela. |
> Gotcha: instance é o nome lógico da entidade, não a tabela. Para @JapeEntity(entity = "CabecalhoNota", table = "TGFCAB"), usa-se @BeforeLoadListener(instance = "CabecalhoNota").
5. Interface FinderListener
package br.com.sankhya.jape.core;
public interface FinderListener {
void beforeExecute(EntityMetaData entity, FinderWrapper finder) throws Exception;
}
| Parâmetro | Tipo | Uso | |:-------------------------|:----------------------------------------------|:--------------------------------------------------------------| | entity | br.com.sankhya.jape.metadata.EntityMetaData | Metadados da entidade interceptada (entity.getName(), etc.).| | finder | br.com.sankhya.jape.util.FinderWrapper | Builder da consulta — compor critérios via finder.getWhere()/finder.setWhere(...). |
- Método declara
throws Exception— exceção lançada bloqueia a busca e propaga ao chamador. finder.setWhere(String clause)substitui a cláusula WHERE inteira — para adicionar critério, componha comfinder.getWhere()+" AND ...". Prefixothis.obrigatório em todo campo da clause (mesma convenção do@Criteria— verrepository).
6. Filtros dinâmicos por contexto do usuário
Use o finder para critérios baseados no usuário logado:
@Log
@BeforeLoadListener(instance = "TdcXyzPedido")
public class TdcXyzPedidoFinderListener implements FinderListener {
@Override
public void beforeExecute(EntityMetaData entity, FinderWrapper finder) throws Exception {
BigDecimal codUsu = JapeSession.getContext().getUserID();
String where = finder.getWhere();
String criterio = "this.CODUSUCAD = " + codUsu;
finder.setWhere(where == null || where.isEmpty() ? criterio : where + " AND " + criterio);
log.info("Buscas de " + entity.getName() + " filtradas pelo usuario " + codUsu);
}
}
> Para SQL portável (Oracle/MSSQL) na clause — datas, nullValue, ignorecase, etc. — use macros Sankhya em vez de sintaxe específica de banco (SYSDATE, NVL, ||). Ver skill macros.
7. Injeção de dependência
Assim como o @Listener de persistência, @BeforeLoadListener suporta @Inject (Guice). Delegue lógica de leitura de contexto/config a um service ou repository injetado via construtor:
import com.google.inject.Inject;
import lombok.extern.java.Log;
@Log
@BeforeLoadListener(instance = "TdcXyzContrato")
public class TdcXyzContratoFinderListener implements FinderListener {
private final EscopoUsuarioService escopoService;
@Inject
public TdcXyzContratoFinderListener(EscopoUsuarioService escopoService) {
this.escopoService = escopoService;
}
@Override
public void beforeExecute(EntityMetaData entity, FinderWrapper finder) throws Exception {
String filialClause = escopoService.clauseFiliaisPermitidas(); // ex.: "this.CODFIL IN (1, 3)"
String where = finder.getWhere();
finder.setWhere(where == null || where.isEmpty() ? filialClause : where + " AND " + filialClause);
}
}
@Injectdecom.google.inject.Inject— nuncajavax.inject.Inject.- Dependências
private final, injetadas via construtor. Nuncanewem dependência gerenciada. EscopoUsuarioServiceregistrado no módulo Guice do projeto (verdependency-injection).
8. Benefícios
- ✅ Injeção de dependências:
@Injectpara acessar services e repositories. - ✅ Configuração zero: sem editar XML de entidade para registrar o listener — a anotação automatiza o atributo
finder-listener. - ✅ Segurança tipada: o SDK valida em compile-time que a classe implementa
FinderListenere queinstancefoi informado.
9. Performance — método roda em TODA busca
beforeExecute é chamado em todas as buscas da entidade. Mantenha o método leve.
- Evite consultas pesadas ao banco dentro de
beforeExecute. - Cacheie dados estáveis (config, perfis) em vez de reconsultar a cada busca.
- Lógica complexa → delegar a service injetado, com cache quando aplicável.
10. Anti-Patterns (PROIBIDO)
| Anti-Pattern | Correção | |:-------------------------------------------------------------------|:------------------------------------------------------------------| | Usar em instância nativa (Parceiro, Produto, CabecalhoNota) | Só instâncias do próprio add-on | | Múltiplos @BeforeLoadListener para a mesma instância | Apenas um por entidade — múltiplos = erro de compilação | | Esquecer de declarar a entidade no dicionário (XML / @JapeEntity)| Sem declaração o listener não é injetado | | Consulta pesada ao banco dentro de beforeExecute | Método roda em toda busca — manter leve / cachear | | Omitir prefixo this. na clause do finder.setWhere(...) | Usar this.CAMPO = ... — obrigatório | | setWhere(criterio) direto, descartando o filtro existente | Compor: getWhere() + " AND " + critério | | SQL específico de banco na clause (SYSDATE, NVL, ROWNUM) | Usar macros Sankhya portáveis (dbDate(), nullValue(), etc.) | | new em dependência gerenciada | @Inject via construtor (Guice) | | @Inject de javax.inject | Usar com.google.inject.Inject | | System.out / SLF4J para log | @Log Lombok + java.util.logging |
11. Checklist: Novo @BeforeLoadListener
- [ ] Entidade-alvo declarada no dicionário (XML ou
@JapeEntity) e é instância do próprio add-on. - [ ] Classe implementa
br.com.sankhya.jape.core.FinderListener. - [ ] Anotada com
@BeforeLoadListener(instance = "")— nome =@JapeEntity(entity = "...")(ou `` no XML), não a tabela. - [ ] Apenas um
@BeforeLoadListenerpara a instância em todo o projeto. - [ ]
beforeExecutecompõe critérios viagetWhere()+" AND "+setWhere(...)(nuncasetWhereseco — substitui o filtro existente), com prefixothis.nos campos. - [ ] Macros portáveis no lugar de SQL específico de banco.
- [ ] Método leve — sem consultas pesadas (roda em toda busca).
- [ ] Dependências (service/repository) via
@Injectconstrutor (Guice), se necessárias. - [ ]
@LogLombok para logging (java.util.logging). - [ ] Filtros dinâmicos por usuário via
JapeSession.getContext().getUserID().
Skills relacionadas
entity— entidade-alvo precisa estar declarada via@JapeEntitydata-dictionary— declaração XML; atributofinder-listenerautomatizado pela anotaçãorepository— sintaxethis.CAMPOda clause (igual ao@Criteria); alternativa por-query ao filtro transversalmacros— SQL portável Oracle/MSSQL na clause dofinder.setWhere(...)listener—@Listener(eventos CRUD de persistência — escopo distinto: gravação, não busca)business-rule— hook transacional comercial (escopo distinto)dependency-injection— wiring Guice do service/repository injetado no listenertest— JUnit + Mockito dobeforeExecute
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: snk-devcenter
- Source: snk-devcenter/addon-studio
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.