Tutorial - VSet Chameleon
Introdução ao VSet Chameleon
Este tutorial tem objetivo demonstrar o desenvolvimento de um módulo no VSet Chameleon.
Vamos desenvolver o módulo de CEPG (Gestão de Código de Endereço Postal Global) básico durante neste tutorial.
Intenção não é aprofundar o conhecimento do CEPG e sim aprender como implementá-lo usando as bibliotecas disponíveis no VSet Chameleon Business Process Suite.
Neste tutorial, o foco é o desenvolvimento no back-end e não no front-ends ou canais.
Pré requisitos
- Conhecimento da linguagem java.
- Noção da arquitetura do VSet Chameleon Business Process Suite.
- Instalação do ambiente de desenvolvimento VSet Chameleon - Usando Eclipse como IDE.
Antes de começar o tutorial
Durante neste tutorial, vamos constuir pequeno sistema CEPG (Gestão de Código de Endereço Postal Global) que possibilitará você adquirir as técnicas e os conhecimentos que são fundamentais para o desenvolvimento de qualquer projeto ou módulo no VSet Chameleon Business Process Suite desde da sua simplicidade até alta complexidade.
O tutorial estará dividido em seções:
- Revisando os conceitos do processo de negócio VSet Chameleon.
- Criando módulo CEPG.
- O que vamos construir no CEPG?
- Criando classe de manipulação de dados da tabela de banco de dados.
- Criando classe de autenticação.
- Criando classe de processos de negócio.
- Criando classe de manipulação de dados nos arquivos.
- Criando classe de serviço.
- Criando classe de notificações.
- Gerando pacote de deploy.
- Implementando api para parceiro e terceiros.
- Gerando biblioteca de api para canais/front-ends (REST).
Caso você tenha algum conhecimento da arquitetura e/ou bibliotecas do VSet Chameleon, não é necessário seguir todas as seções.
Procura entender como funciona o processo executado pelo VSet Chameleon. Isso ajudará muito a sua assimilação do conhecimento e técnicas para o desenvolvimento de qualquer projeto no VSet Chameleon.
Revisando os conceitos do processo de negócio VSet Chameleon
Partindo da premissa da simplicidade, inspiramos o modelo que sempre aprendemos na primeira aula de programação o conceito de processo onde qualquer atividade, ou conjunto de atividades, que tenha ou não uma entrada, agrega algum valor e fornece ou não uma saída. Em nível mais abstrato, a atividade processa ou transforma algo a partir de uma entrada gerando uma saída conforme a figura abaixo:

Temos que ter em nossa mente este conceito para entender como funciona as classes das bibliotecas do VSet Chameleon. Todas delas foram inspiradas neste conceito de entrada (input), processamento (process) e saída (output) de acordo com contexto que irá atuar.
Contexto da classe de negócio
Partindo no conceito de entrada (input), processamento (process) e saída (output), foram implementadas várias classes na biblioteca do VSet Chameleon de acordo com contexto. Eis os contextos:
Processo ou regra de negócio
- entrada (input) - Estrutura de dados da entrada do processo
- processamento (process) - Executar as regras de negócio.
- saída (output) - Estrutura de dados da saída do resultado do processo.
- entrada (input) - Estrutura de dados da entrada do processo
Autenticação
- entrada (input) - Estrutura de dados da entrada para autenticação
- processamento (process) - Executar o processo de autenticação.
- saída (output) - Estrutura de dados da saída do resultado da autenticação.
- entrada (input) - Estrutura de dados da entrada para autenticação
Banco de dados
- entrada (input) - Parâmetros de entrada da execução da query.
- processamento (process) - Sua função é executar uma query definida na classe.
- saída (output) - Estrutura de dados da saída do resultado da execução da query.
- entrada (input) - Parâmetros de entrada da execução da query.
Arquivo
- entrada (input) - Estrutura de dados da entrada para modo leitura.
- processamento (process) - Executar processo de manipulação de dados do arquivo, seja no modo leitura ou gravação.
- saída (output) - Estrutura de dados da saída para modo de gravação.
- entrada (input) - Estrutura de dados da entrada para modo leitura.
Notificação
- entrada (input) - Estrutura de dados para envio da notificação.
- processamento (process) - Executar o processo de notificação de mensagem.
- saída (output) - Não se aplica.
- entrada (input) - Estrutura de dados para envio da notificação.
API
- entrada (input) - Estrutura de dados da entrada do processo
- processamento (process) - Executar as regras de negócio.
- saída (output) - Estrutura de dados da saída do resultado do processo.
- entrada (input) - Estrutura de dados da entrada do processo
Analisando o ciclo de vida da classe BusinessProcessWrapper
Classe de negócio BusinessProcessWrapper é a classe principal do processo de negócio onde framework VSet Chameleon executa a orquestração ou coreografia do processo garantindo o seu contexto transacional. Ela é uma classe abstrata que não pode ser instanciada.
Todas as classes de negócio deverão sempre herdar a classe BusinessProcessWrapper.
Para atender todos os cenários possíveis de processo de negócio em uma única classe, foram criados as fases (métodos) que oferecem a flexibilidade de acordo com a sua necessidade.
Seguem as fases (métodos) a saber:
- builtIn – implementa atividades quando é iniciado a instância da classe;
- enhancementLayer - implementa alguma atividade antes de processar o método
validate(). O principal objetivo é implementar alguma atividade ou rotina de melhoria como por exemplo: segurança; - validate - implementa o que tange validação necessária de um algo
- beforeProcess - implementa alguma atividade antes de processar o método
process(); - process - implementa a regra de negócio propriamente dita;
- afterProcess - implementa alguma atividade ou rotina após a execução do método
process(); - finalize - implementa alguma atividade ou rotina para finalização;
- rollback - implementa alguma atividade ou rotina de desfazimento caso houver alguma exceção na classe de negócio;
- cleanVariables - libera os objetos instanciados para garbage collection executar a exclusão dos mesmos na memória.
Quando o VSet Chameleon recebe uma requisição para executar o processo, serão executadas as fases (métodos) na forma sequencial:
- enhancementLayer
- validate
- beforeProcess
- process
- afterProcess
- finalize
- cleanVariables
Se houver alguma exceção por parte da execução da classe de negócio, VSet Chameleon executará o método rollback() se houver para desfazer o contexto transacional.
O método process() é o principal método que executa as regras de negócio da classe de negócio.
Quando uma classe é requisitada pelo canal/front-ends no VSet Chameleon, é verificado na sua fábrica se já foi instanciada a classe. Caso seja pela primeira vez, VSet Chameleon instancia a classe e a adiciona no cache de classes e por sua vez, executa a fase (método) builtIn()
Convenção de código
Além de usar a boa prática da convenção de código do Java, é sugerido usar também para VSet Chameleon no intuído de ficar no padrão e facilitar a manutenibilidade do código.
Para definição de nome da classe, atributos e métodos utilizamos padrão CamelCase.
Criando módulo CEPG
Vamos criar o projeto CEPG (Gestão de Código de Endereço Postal Global) de acordo com passo a passo da criação do módulo de negócio e/ou software.
Para incorporar build.xml no projeto, segue o link template do build.xml e ajustar o nome do projeto conforme o exemplo abaixo:
<property name="jar.name" value="cepg" />
<property name="vendor.code" value="VICAX" /> <!-- código da sua empresa -->
<property name="module.code" value="cepg" />
<property name="module.prefix" value="CEPG" />
<property name="module.version" value="1.0.0" />
<property name="vendor.name" value="Vicax Tecnologia e Gestão" /> <!-- nome da sua empresa -->
<property name="module.name" value="CEPG" />
<property name="module.description" value="Gestão de Código de Endereço Postal Global" />
<property name="built.by" value="Vicax Tecnologia e Gestão" /> <!-- nome da sua empresa ou equipe/departamento responsável -->
O que vamos construir no CEPG?
Vamos construir o módulo bem básico de Gestão de Código de Endereço Postal Global (CEPG) cujo seu objetivo é gerenciar o cadastro código de endereço postal global.
Banco de dados
Vamos criar quatro tabelas no projeto a saber:
| Tabela | Descrição |
|---|---|
| Country | Tabela de países |
| State | Tabela de estados ou províncias |
| County | Tabela de municípios ou condados |
| ZipCode | Tabela de código de endereço postal |

Criando classe de manipulação de dados da tabela de banco de dados
Vamos mostrar como implementar a classe de manipulação de dados das tabelas do projeto CEPG. Nesta classe permitirá fazer o cadastramento e manutenção das tabelas Country, State, Country e ZipCode.
Na biblioteca do VSet Chameleon para banco de dados tem classes para CRUD e SQL genérica. Vamos utilizar essas duas opções ao decorrer no desenvolvimento.
Nota: Existe ferramenta para gerar as classes na forma automática a partir do banco de dados. Neste tutorial vamos implementar manualmente para entender os conceitos dos elementos que fazem parte da classe.
Padrão CRUD (Create Read Update Delete)
Para criar uma classe para tabela Country no padrão CRUD, vamos herdar a classe CRUDDAOWrapper que é responsável para manipular os dados de acordo com a operação solicitada.
Vamos ao passo a passo.
Criando uma classe de estrutura de dados
- Vamos chamar esta classe como Country igual o nome da tabela do banco de dados que criamos e nela utilizar o padrão POJO1.
public class Country {
}
- Inserindo os atributos da tabela Country na classe de acordo com seu tipo.
private int code;
private String name;
private String alpha2Code;
private String alpha3Code;
private Integer ddiCode;
private Integer revenueCode;
private Timestamp lastUpdateDate;
- Atribuindo as anotações em cada atributo.
@ColumnCRUDDAO(name="Code", description="Country code", orderInsert=1, orderUpdate=7, orderDelete=1, orderSelect=1, primaryKey=true)
private int code;
@ColumnCRUDDAO(name="Name", description="Country name", orderInsert=2, orderUpdate=1)
private String name;
@ColumnCRUDDAO(name="Alpha2Code", description="Alpha2 code", orderInsert=3, orderUpdate=2)
private String alpha2Code;
@ColumnCRUDDAO(name="Alpha3Code", description="Alpha3 code", orderInsert=4, orderUpdate=3)
private String alpha3Code;
@ColumnCRUDDAO(name="DdiCode", description="DDI code", orderInsert=5, orderUpdate=4)
private Integer ddiCode;
@ColumnCRUDDAO(name="RevenueCode", description="Revenue code", orderInsert=6, orderUpdate=5)
private Integer revenueCode;
@ColumnCRUDDAO(name="LastUpdateDate", description="Last update date", orderInsert=7, orderUpdate=6)
@ReadOnly
private Timestamp lastUpdateDate;
A inclusão da anotação @ColumnCRUDDAO em cada atributo é para informar o seu metadado2 para classe CRUDDAOWrapper.
Observe que as propriedades orderInsert, orderUpdate, orderDelete e orderSelect informam a posição do campo de cada operação à classe devido ao processo de execução da query que é feito por modo prepare3.
Outras propriedades tais como: primaryKey e autoIncrement, sendo que a primeira informa se o atributo é chave primária e a segunda se é auto incremento4.
No caso da tabela Country não tem atributo com caracteristica de auto incremento.
Atenção: A propriedade name tem que ser igual ao nome da coluna da tabela de banco de dados. Quanto à relação do nome da variável (POJO) não necessariamente precisa ser igual do nome da coluna da tabela. Recomendamos se o nome da coluna da tabela for do padrão CamelCase aplicaria também o mesmo nome para a variável.
Para saber maiores detalhes sobre a classe, consulte aqui.
- Colocando os métodos de acesso dos membros (getters e setters).
public int getCode() {
return code;
}
public void setCode(String code) throws ValidationException {
if (code != null && !code.trim().isEmpty()) {
try {
this.code = Integer.parseInt(code);
}
catch (Exception e) {throw new ValidationException(10, "campo Code inválido --- " + code);}
}
}
public void setCode(int code) {
this.code = code;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAlpha2Code() {
return alpha2Code;
}
public void setAlpha2Code(String alpha2Code) {
this.alpha2Code = alpha2Code;
}
public String getAlpha3Code() {
return alpha3Code;
}
public void setAlpha3Code(String alpha3Code) {
this.alpha3Code = alpha3Code;
}
public Integer getDdiCode() {
return ddiCode;
}
public void setDdiCode(String ddiCode) throws ValidationException {
if (ddiCode != null && !ddiCode.trim().isEmpty()) {
try {
this.ddiCode = Integer.parseInt(ddiCode);
}
catch (Exception e) {throw new ValidationException(10, "campo DdiCode inválido --- " + ddiCode);}
}
}
public void setDdiCode(int ddiCode) {
this.ddiCode = ddiCode;
}
public void setDdiCode(Integer ddiCode) {
this.ddiCode = ddiCode;
}
public Integer getRevenueCode() {
return revenueCode;
}
public void setRevenueCode(String revenueCode) throws ValidationException {
if (revenueCode != null && !revenueCode.trim().isEmpty()) {
try {
this.revenueCode = Integer.parseInt(revenueCode);
}
catch (Exception e) {throw new ValidationException(10, "campo RevenueCode inválido --- " + revenueCode);}
}
}
public void setRevenueCode(int revenueCode) {
this.revenueCode = revenueCode;
}
public void setRevenueCode(Integer revenueCode) {
this.revenueCode = revenueCode;
}
public Timestamp getLastUpdateDate() {
return lastUpdateDate;
}
public void setLastUpdateDate(String lastUpdateDate) throws ValidationException {
if (lastUpdateDate != null && !lastUpdateDate.trim().isEmpty()) {
try {
Object obj = Format.parseDateTime(lastUpdateDate, Format.DATETIME);
this.lastUpdateDate = new Timestamp(((Date) obj).getTime());
}
catch (Exception e) {throw new ValidationException(10, "campo LastUpdateDate inválido --- " + lastUpdateDate);}
}
}
public void setLastUpdateDate(Timestamp lastUpdateDate) {
this.lastUpdateDate = lastUpdateDate;
}
Nota: Observe que os atributos com tipo inteiro, double, byte, Date tem mais de um método de sobrecarga5 para garantir interoperabilidade entre os canais. Por exemplo: um canal pode mandar o valor do atributo Code no formato inteiro e outro canal com formato string.
Implementando a classe herdada pela CRUDDAOWrapper.
- Vamos chamar esta classe como CountryDAO e implementar as informações necessárias para manupular os dados da tabela Country.
public class CountryDAO extends CRUDDAOSQLWrapper {
}
- Incluindo as anotações na classe CountryDAO
Temos que informar nome da tabela, composto por schema e nome (schema.nome), que a classe será responsável para manupulará os dados da tabela. Para isso, existem duas anotações: @AuditTable e @Table.
Anotação @AuditTable informa para classe que todas as operações de inclusão, alteração e exclusão serão gravados no log para que no futuro possam ser auditadas os dados. Ela tem duas propriedades, name e id, sendo que o primeira é schema e nome (schema.nome) da tabela e a segunda é o abributo que será como chave para pesquisar. Tem que ser tipo numérico. Recomendamos que seja sempre o campo que faz parte da chave primária. Caso a tabela tenha uma chave primaria composta, recomendamos que seja a chave que é agregadora. Essa anotação é opcional.
No exemplo da tabela Country a propriedade id seria o atributo Code.
Quanto à anotação @Table, informa schema e nome (schema.nome) da tabela para classe.
Caso deseja que esta classe esteja disponível para os canais/front-ends, existe uma terceira anotação @BusinessProcessName para incluir na classe. Esta anotação informa para classe registrar no VSet Chameleon a lista de classes acessíveis aos canais/front-ends. A propriedade name é o nome que será enviado pelos canais/front-ends para solicitar uma requisição.
@AuditTable(name="core.Country", id="Code")
@Table(name="core.Country")
@BusinessProcessName(name="CountryDAO", description="CRUD tabela - Country", channel=true)
public class CountryDAO extends CRUDDAOWrapper {
}
Para saber maiores detalhes sobre a classe, consulte aqui.
- Incluindo os métodos na classe CountryDAO
- Métodos de entrada e saída de estrutura de dados da tabela.
private Country input = new Country();
private ArrayList<Country> output = new ArrayList<Country>();
public Country getInput() {
return input;
}
public ArrayList<Country> getOutput() {
return output;
}
Objeto Country que definimos na seção Criando uma classe de estrutura de dados será a mesma estrutura para entrada (input) quanto a saída (output). Observe que na estrutura de saída é um array de objeto Country por causa da operação lista (list).
Lembrando que vimos no contexto, input são os parametros que fazem parte da query enquanto output são os campos retornado pelo resultado da execução da query.
Nota: Todos os objetos que referenciam a estrutura de dados deverão ser instanciados na hora da criação da instancia da classe.
- Método para recuperar alias do banco de dados.
@Override
protected String getAlias() {
return "TRADING";
}
Foi implementado o conceito alias para que a classe tenha independência caso haja alguma mudança do banco de dados, seja na mudança de servidor ou de banco de dados. Neste exemplo usamos alias com nome de "TRADING".
O cadastramento da alias é feita no arquivo de configuração do VSet. Vide seção alias
- Método informar a mensagem caso não encontrado na consulta ou lista da tabela.
@Override
protected String getNotFound() {
return "There is no Country.";
}
Caso não encontre o registro na tabela, VSet Chameleon lança com a mensagem definida no getNotFound().
- Métodos de execução de fases anterior e posterior do processo de manipulação de dados da tabela
public void beforeProcess() throws Exception {
}
public void afterProcess() throws Exception {
}
Estes métodos permitem implementar regras ou processo se forem necessários para atender um determinado requisito.
O método beforeProcess é responsável por executar algo antes do processo de manipulação de dados da tabela, enquanto o afterProcess é responsável por executar após o processo de manipulação.
Elas são opcionais.
As sintaxes de inclusão, alteração, exclusão, seleção e lista são montadas dinamicamente na instanciação da classe.
Criando classe de autenticação
Se desejamos que o usuário seja autenticado antes de usar as funcionalidades disponíveis pelo projeto/módulo, VSet Chameleon disponibiliza na sua biblioteca uma classe para fazer este processo de autenticação chamada AuthenticationProcessAbstract.
Neste tutorial vamos criar uma classe que irá herdar a classe AuthenticationOAuth2Process que tem a implementação do conceito OAuth 2.0. e por sua vez, a classe também foi herdada da classe AuthenticationProcessAbstract.
Neste projeto vamos criar uma classe de entrada chamada de AuthInput, onde usuário digitará código do usuário (usercode) e a senha (password).
public class AuthInput {
private String usercode;
@Confidential
private String password;
public String getUsercode() {
return usercode;
}
public void setUsercode(String usercode) {
this.usercode = usercode;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
Na estrutura de dados de saída, vamos criar uma classe AuthOutput onde retornará as seguintes informações: nome do usuário (username), foto (photo), ultimo acesso (lastLoginDate) e identificador da notificação (objectId).
public class AuthOutput {
private String username;
private String photo;
private Timestamp lastLoginDate;
private String objectId;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPhoto() {
return photo;
}
public void setPhoto(String photo) {
this.photo = photo;
}
public Timestamp getLastLoginDate() {
return lastLoginDate;
}
public void setLastLoginDate(Timestamp lastLoginDate) {
this.lastLoginDate = lastLoginDate;
}
public String getObjectId() {
return objectId;
}
public void setObjectId(String objectId) {
this.objectId = objectId;
}
}
Agora vamos criar a classe Auth herdando a classe AuthenticationOAuth2Process que é responsável do processo de autenticação padrão OAuth 2.0.
@BusinessProcessName(name="Auth", description="user authentication", channel=true, tag="AUTH")
public class Auth extends AuthenticationOAuth2Process {
private AuthInput input = new AuthInput();
private AuthOutput output = new AuthOutput();
@Override
public AuthInput getInput() {
return input;
}
@Override
public AuthOutput getOutput() {
return output;
}
@Override
public void authenticate() throws Exception {
if (!input.getUsercode().equals("42a50438-d3ec-3e0c-aea8-783c5e6810f1") ||
!input.getPassword().equals("W$vA9dS7aI0vO3tL2sJ5nK1tQ0oC4lJ8hA8iT0sI8wY2uG4gE3"))
throw new ValidationException(15, "Usuário/senha inválido.");
setSessionPropertyInt("IdOrganization", 1);
setUsername("FULANO BELTRANO");
}
@Override
public void setConfiguration() throws Exception {
super.setConfiguration();
setExpires(0);
}
}
Nesta classe deverá ser implementado dois métodos a saber: authenticate() e setConfiguration().
O método authenticate() é responsável pela regra de autenticação e setConfiguration() pela configuração da autenticação tal como validade do token, por exemplo.
Foi incluido a anotação @BusinessProcessName para disponibilizar a classe aos canais/front-end.
Observe que a classe retornará os campos token_type, expires_in, scope, access_token, timestamp e array de transactionView da classe AuthenticationOAuth2Process além dos campos definidos da classe de saída AuthOutput.
Exemplo json da mensagem de saída da autenticação.
{"token_type":"bearer","expires_in":0,"scope":"","access_token":"oJ9FKg1eVSZCzENUzO9QafTqC2E2DQ0OJy1iPx1IAFxaBPEHEE2sxZcB2reofSaEF5QJqdi6nO6d/r5Lmamm6ioq-i7kSJs8TqMFHPdBCA1FtHDghiA9P6FFipLBy1XgjsEjRXonVF+upgEL476mrRTCdlwYpmrYtB7ax1eYYqoH+i8IoRVmH1RlhFBdq2zZapgUq-PRdHHSR6MTX8xdAaBYDwrVYNMyLOlMqgbLz+jLUYNIjGlg0pALlf5pFQg+PucYyoO6MRsyEPjJuFJsy3eHFMyXCtqtSWe5n+MNTRh47h7fPS14czBFplaMaV6mI+iHAJxZhAMkoH8yDZbORmJJfPbbWJ/bN5XgeqfpcBs7t7muzcyPyznGfb+TGpM2nEp1HTqN+DeogdGAgJ1SghSo8TNCjsEll2AMC1IOd1sL7Qj0Xsbh7cz5LUFiQlGfAqC7GObEFDGY+1mPhjkq3XAD0jdbI6qF6r0P1QbT+0NltGO8ramhIetDc9S/uUSaIqwF3W7dun98ddb2U3DSZ28dQS+ioq","timestamp":"2022-01-31T16:35:59-0300","transactionView": [], "username":"FULANO BELTRANO","photo":"/image/O8vR3rW2dA2gP5sY1mA5fQ4tI4pI2lL7kJ5p.jpeg","lastLoginDate":"2019-11-23T10:58:29-0300"}
Criando classe de processos de negócio
A classe BusinessProcessWrapper tem a sua finalidade de implementar algum processo e/ou regra de negócios garantindo o contexto transacinal do processo como todo.
Ponto forte desta classe é fazer o papel de abstração da complexidade com intuíto de simplificar o desenvolvimento do lado dos canais/front-ends e também garantir que qualquer mudança por parte do backend não afetará a integração com os canais/front-ends.
Vamos criar uma classe chamada ZipCodeInfo onde canal/front-end vai solicitar os dados do endereço postal. Esta classe abstrairá como será feita a busca dos dados do endereço postal se será por banco de dados ou api de terceiros.
Para obter os dados do endereço postal, terá informar código de cep (zipCode) e país (country). Vamos criar a classe de entrada para coletar essas informações: ZipCodeInfoInput.
public class ZipCodeInfoInput {
private String zipCode;
private String countyCode;
public String getZipCode() {
return zipCode;
}
public void setZipCode(String zipCode) {
this.zipCode = zipCode;
}
public String getCountyCode() {
return countyCode;
}
public void setCountyCode(String countyCode) {
this.countyCode = countyCode;
}
}
Agora vamos criar uma classe ZipCodeInfoOutput que será a estrutura de dados de saída após do processo de coletagem de dados do endereço postal solicitado.
public class ZipCodeInfoOutput {
private String zipCode;
private String address;
private String district;
private String city;
private String state;
private String countyCode;
private String dialingCode;
private String latitude;
private String longitude;
private boolean edit;
public String getZipCode() {
return zipCode;
}
public void setZipCode(String zipCode) {
this.zipCode = zipCode;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getDistrict() {
return district;
}
public void setDistrict(String district) {
this.district = district;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getCountyCode() {
return countyCode;
}
public void setCountyCode(String countyCode) {
this.countyCode = countyCode;
}
public String getDialingCode() {
return dialingCode;
}
public void setDialingCode(String dialingCode) {
this.dialingCode = dialingCode;
}
public String getLatitude() {
return latitude;
}
public void setLatitude(String latitude) {
this.latitude = latitude;
}
public String getLongitude() {
return longitude;
}
public void setLongitude(String longitude) {
this.longitude = longitude;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
public boolean isEdit() {
return edit;
}
public void setEdit(boolean edit) {
this.edit = edit;
}
}
Após da criação das classes de entrada (ZipCodeInfoInput) e saída (ZipCodeInfoOutput), segue a classe ZipCodeInfo.
@BusinessProcessName(name="ZipCodeInfo", description="Obter dados de endereço pelo cep", channel=true)
public class ZipCodeInfo extends PublicProcess {
private ZipCodeInfoInput input = new ZipCodeInfoInput();
private ZipCodeInfoOutput output = new ZipCodeInfoOutput();
private ZipCodeDAO zipDAO = new ZipCodeDAO();
@Override
public ZipCodeInfoInput getInput() {
return input;
}
@Override
public ZipCodeInfoOutput getOutput() {
return output;
}
@Override
public void initialize() throws Exception {
}
@Override
public void beforeProcess() throws Exception {
}
@Override
public void process() throws Exception {
}
@Override
public void afterProcess() throws Exception {
}
@Override
public void finalize() throws Exception {
}
@Override
public void validate() throws Exception {
}
@Override
public void rollback() throws Exception {
}
@Override
public void cleanVariables() throws Exception {
}
}
Vamos agora implementar a abstração do processo de coletagem dos dados de endereço postal no método process().
@Override
public void process() throws Exception {
ZipCodeDAO zipDAO = new ZipCodeDAO();
zipDAO.getInput().setCountryCode(input.getCountryCode());
zipDAO.getInput().setCode(input.getZipCode());
try {
zipDAO.select();
ZipCode zip = (ZipCode) zipDAO.getOutputOnlyRecord();
output.setZipCode(zip.getCode());
output.setAddress(zip.getAddress());
output.setDistrict(zip.getDistrict());
output.setCity(zip.getCity());
output.setState(zip.getState());
output.setEdit(zip.getAddress() == null || zip.getAddress().trim().isEmpty());
output.setCountyCode(zip.getCountyCode());
output.setDialingCode(zip.getDialingCode());
output.setLatitude(zip.getLatitude());
output.setLongitude(zip.getLongitude());
}
catch (NotFoundException e) {
try {
final String USER_AGENT = "Mozilla/5.0";
String url = "https://apps.globalzip.com/search/api/zipcode.json?countrycode=" + input.getCountryCode() + "&code=" + input.getZipCode();
URL obj = new URL(url);
HttpURLConnection conn = (HttpURLConnection) obj.openConnection();
conn.setRequestMethod("GET");
conn.setRequestProperty("User-Agent", USER_AGENT);
int responseCode = conn.getResponseCode();
System.out.println("\nSending 'GET' request to URL : " + url);
System.out.println("Response Code : " + responseCode);
BufferedReader in = null;
try {
in = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF8"));
String inputLine;
StringBuffer response = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
System.out.println(response.toString());
JSONObject j = null;
j = new JSONObject(response.toString());
System.out.println("status: " + j.getInt("status"));
output.setEdit(false);
if (j.getString("statusText").equalsIgnoreCase("ok")) {
output.setZipCode(j.getString("code"));
output.setAddress(j.getString("address"));
output.setDistrict(j.getString("district"));
output.setCity(j.getString("city"));
output.setState(j.getString("state"));
output.setEdit(j.getString("address") == null || j.getString("address").trim().isEmpty());
}
else
throw new ValidationException(18, j.getString("message") + " - " + input.getZipCode());
}
finally {
in.close();
}
}
catch (Exception ex) {
ex.printStackTrace();
if (ex instanceof ValidationException)
throw ex;
else
throw new ValidationException(19, "Zip code lookup process failure");
}
}
catch (Exception e) {
e.printStackTrace();
if (e instanceof ValidationException)
throw e;
else
throw new ValidationException(21, "Zip code lookup process failure");
}
}
Observe que esta classe fez uma abstração do processo de coletagem dos dados de endereço postal para o canal/front-end. O objetivo da classe é retornar os dados independentemente a forma que foi coletado a informação. No cenário deste processo, quando é requisitado, verifica primeiro na base de dados se tem código postal e caso não tiver acessa uma api para buscar a informação por meio da api externo.
Criando classe de manipulação de dados nos arquivos
Vamos criar uma classe para importar um arquivo cujo os campos são separados pelo delimitador ";" para tabela ZipCode do banco de dados. Essa classe deverá ser herdada a classe FileDelimiterWrapper para fazer manipulação dos dados.
De acordo com conceito do contexto de arquivo, os campos do registro são entrada do processo de manipulação de dados do arquivo.
Vamos criar a classe de estrutura de dados do registro que vamos chmar de ZipCodeFileDetail.
public class ZipCodeFileDetail {
private String zipCode;
private String address;
private String district;
private String city;
private String state;
private String countyCode;
private String dialingCode;
private String latitude;
private String longitude;
public String getZipCode() {
return zipCode;
}
public void setZipCode(String zipCode) {
this.zipCode = zipCode;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getDistrict() {
return district;
}
public void setDistrict(String district) {
this.district = district;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getCountyCode() {
return countyCode;
}
public void setCountyCode(String countyCode) {
this.countyCode = countyCode;
}
public String getDialingCode() {
return dialingCode;
}
public void setDialingCode(String dialingCode) {
this.dialingCode = dialingCode;
}
public String getLatitude() {
return latitude;
}
public void setLatitude(String latitude) {
this.latitude = latitude;
}
public String getLongitude() {
return longitude;
}
public void setLongitude(String longitude) {
this.longitude = longitude;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
}
Agora vamos criar a classe ZipCodeFile herdada pela classe FileDelimiterWrapper.
public class ZipCodeFile extends FileDelimiterWrapper {
private ZipCodeFileDetail detail = new ZipCodeFileDetail();
public ZipCodeFile(String filename,
boolean carriageReturn_newline, MODETYPE mode, String delimiter) throws Exception {
super(filename, carriageReturn_newline, mode, delimiter);
}
public ZipCodeFileDetail getDetail() {
return detail;
}
}
Nota: Como um arquivo pode ter mais de uma estrutura de dados de registro diferente, então anotação @FileFieldType tem a sua finalidade de informar os parametros que identifica a estrutura de dados do registro. No caso do tutorial, como só temos um tipo de registro então não há necessidade de incluir anotação.
Depois de criar a classe para manipular o arquivo, vamos exemplificar como ler o arquivo.
// create instance
ZipCodeFile file = new ZipCodeFile("C:/Users/vicax/all_zips.csv", true, MODETYPE.READER, ";");
// reference record detail
ZipCodeFileDetail detail = file.getDetail();
// read record
while (file.readLine() != null) {
System.out.println("Record: " + detail.getAddress() + " " + detail.getDistrict() + " " + detail.getCity());
}
// close file
file.close();
O método file.readLine() fará leitura de cada registro do arquivo e disponibilizará os dados por meio da variável detail.
Além de ler também podemos gerar o arquivo. Eis o exemplo:
// create instance
ZipCodeFile file = new ZipCodeFile("C:/Users/vicax/output/all_zips.csv", true, MODETYPE.WRITER, ";");
// reference record detail
ZipCodeFileDetail detail = file.getDetail();
detail.setZipCode("70297-400");
detail.setAddress("Via S1 - Brasília");
detail.setDistrict("Plano piloto");
detail.setCity("Brasília");
detail.setState("DF");
detail.setCountyCode("5300108");
detail.setDialingCode("061");
detail.setLatitude("-15.7801");
detail.setLongitude("-47.9292");
// write record
file.write(detail);
// close file
file.close();
O método file.write(detail) adiciona o registro no formato csv com delimitador ";" no arquivo.
Para maiores informações sobre classe FileDelimiterWrapper, clique aqui
Criando classe de serviço
Vamos criar um serviço para importar o arquivo para tabela de banco de dados. Em cada um minuto, o serviço acordará e verificar se tem o arquivo para processar. Caso tenha processamento com sucesso, o serviço irá mover o arquivo para pasta processado. Vamos usar a classe ZipCodeFile que criamos na seção de manipulação de arquivos para manipular os dados.
Para implementar a classe de serviço ZipCodeLoader, deverá herdar a classe Service da biblioteca VSet. Segue a estrutura da classe de serviço criada:
@ServiceName(name="ZipCodeLoader", description="Zip code loader", batch=true)
public class ZipCodeLoader extends Service {
public ZipCodeLoader() {
super();
setTimeWait(60000); // milliseconds
}
@Override
protected void initialize() throws Exception {
}
@Override
protected void process() throws Exception {
}
@Override
protected void finalize() throws Exception {
}
@Override
protected void recover() throws Exception {
}
@Override
protected void cleanVariables() throws Exception {
}
}
A anotação @ServiceName informa os dados do serviço para VSet Chameleon. Ele é responsável por instanciar o serviço na hora da inicialização do VSet Chameleon. A propriedade batch significa que o serviço só será executado se no arquivo de configuração do VSet Chameleon estiver igual a "true".
Método setTimeWait(60000) está informando que terá uma pausa de 1 minuto do serviço após do termino do processamento. Ciclo de vida do serviço será permanente até parada do VSet, processando de um em um minuto conforme o parametro timeWait. Ela também pode ser interrompido pelo console do VSet que faz a gestão dos serviços ativos no Vset Chameleon ou por meio do parametro shutdown.
Agora vamos implementar a lógica para fazer a carga da tabela a partir do arquivo no método process().
@Override
protected void process() throws Exception {
String filename = "C:/Users/vicax/all_zips.csv";
// checking if file arrived
if (!(new File(filename)).exists())
return;
// create instance
ZipCodeFile file = new ZipCodeFile(filename, true, MODETYPE.READER, ";");
// reference record detail
ZipCodeFileDetail detail = file.getDetail();
// instance zipcode table
ZipCodeDAO zipDAO = new ZipCodeDAO();
ZipCode zip = zipDAO.getInput();
zipDAO.beginBatch("insert", 5000);
try {
// read record
while (file.readLine() != null) {
System.out.println("Record: " + detail.getAddress() + " " + detail.getDistrict() + " " + detail.getCity());
zip.setCode(detail.getZipCode().trim());
zip.setAddress(detail.getAddress());
zip.setDistrict(detail.getDistrict());
zip.setCity(detail.getCity());
zip.setState(detail.getState());
zip.setCountyCode(detail.getCountyCode());
zip.setDialingCode(detail.getDialingCode());
zip.setLatitude(detail.getLatitude());
zip.setLongitude(detail.getLongitude());
zipDAO.addBatch();
}
zipDAO.endBatch();
// move file do folder processed
String folder = "C:/Users/vicax/processed/all_zips.csv[" + Format.getDateTimeNowDisplay("yyyy-MM-dd HH:mm:ss") + "]";
Util.move(filename, folder, true);
}
finally {
// close file
file.close();
}
}
Criando classe de notificações
Vamos aprender como notificar uma mensagem que terminou a carga na tabela para usuário.
Existem vários tipos de notificações a saber: email, SMS, rede social, browser e mobile. Neste tutorial vamos implementar a notificação via browser, conhecido como push notifications.
A biblioteca VSet Chameleon disponibiliza uma classe, NotificationWrapper, que faz notificação via browser. É nela que vamos herdar para implementar a notificação.
Vamos notificar para usuário a lista de quantidade de código postal incluido por região. Para isso, vamos criar a classe ZipCodeLoaderNotificationInput para armazenar essas informações.
public class ZipCodeLoaderNotificationInput {
private String state;
private int amount;
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
public int getAmount() {
return amount;
}
public void setAmount(int amount) {
this.amount = amount;
}
}
Agora criamos a classe, ZipCodeLoaderNotification para notificar o resultado da carga.
public class ZipCodeLoaderNotification extends NotificationWrapper {
private ArrayList<ZipCodeLoaderNotificationInput> input = new ArrayList<ZipCodeLoaderNotificationInput>();
@Override
public ArrayList<ZipCodeLoaderNotificationInput> getInput() {
return input;
}
@Override
protected String getProjectID() {
return "CEPG";
}
}
Observe que a estrutura de entrada (input) é um array de objeto (ZipCodeLoaderNotificationInput) por causa da lista por região.
Segue o exemplo para implementar o envio da mensagem:
ZipCodeLoaderNotification not = new ZipCodeLoaderNotification();
not.setTitle("Loading the data into the table");
not.setBody("256 new zip codes were uploaded");
not.sendNotification();
A mensagem no formato json após da execução do not.sendNotification() enviado para browser:
{"header": {"wsoperationtype": "notification","ticket": "1667","type": "INFO","time": -1},"info": {"title": "Loading the data into the table","body": "256 new zip codes were uploaded","timestamp": "2019-02-01T09:55:13-0300","tag": "ISSUE","": ""},"payload": {}}
Para implementar a notificação, temos que entender o contexto de notificação onde as informações que serão enviados para browser
Nota: Para funcionar a notificação, deverá verificar no arquivo de configuração do VSet Chameleon se o serviço de notificação está ativo.
Gerando pacote de deploy
Implementando api para parceiro e terceiros
Gerando biblioteca de api para canais/front-ends (REST)
- É um referência a objetos que não dependem da herança de interfaces ou classes de frameworks externos. Na verdade são objetos com design simples. Essa classe tem apenas o construtor e seu métodos de acesso dos membros (getters e setters). Wikipédia↩
- Metadados, ou Metainformação, são dados sobre outros dados. Um item de um metadado pode dizer do que se trata aquele dado, geralmente uma informação inteligível por um computador. Os metadados facilitam o entendimento dos relacionamentos e a utilidade das informações dos dados. Wikipédia↩
- Prepare é útil para evitar problemas com argumentos (não só SQL injection) e permite uma melhor utilização para instruções que precisem ser executadas repetidas vezes, aumentando em muito a performance.↩
- O auto incremento permite que um número único seja gerado quando um novo registro é inserido em uma tabela.↩
- Sobrecarga de método permite a existência de vários métodos de mesmo nome, contanto que tenham assinaturas levemente diferentes, ou seja, variando em número, tipo de argumentos, valor de retorno e até variáveis diferentes. Wikipédia↩