Versão 19
Novidades
XMLHttpRequest passa a permitir a utilização de certificados SSL clientes
Na construção de uma instância de XMLHttpRequest, passa a ser permitindo indicar um certificado SSL com o objetivo de identificar o cliente que está realizando a requisição.
Essa é uma exigência comum em diversos Web Services do governo federal e até então era necessário fazer uso do Java via API Enginelet ou integrar com uma plataforma externa como Node.js para realizar esse tipo de requisição.
Exemplo de uma requisição SOAP com o uso de um certificado cliente:
const XMLHttpRequest = require('@nginstack/engine/lib/net/XMLHttpRequest');
const xml = buildXml();
const xhr = new XMLHttpRequest({
pfxPath: File.pathAppend('certificates', 'company.pfx'),
passphrase: 'secret_passphrase'
);
xhr.open('POST', 'https://mdfe-homologacao.svrs.rs.gov.br/ws/mdferetrecepcao/MDFeRetRecepcao.asmx');
xhr.setRequestHeader('Content-Type', 'application/soap+xml');
xhr.setRequestHeader('SOAPAction', 'urn:MDFeRetRecepcao');
xhr.send(xml);
xhr.responseText; // => xml com resultado do processamento
API de streaming para processamento de XML
Para arquivos XML de tamanho elevado, a classe Document é ineficiente por exigir que todo o XML seja processado e alocado em memória antes que o desenvolvedor possa utilizar os seus métodos para extrair os dados desejados.
Para esse cenário de uso foi disponibilizada a nova API XMLStreamReader, baseada no StAX. Por meio dela, os dados contidos em um arquivo local podem ser processados iterativamente:
const XMLStreamReader = require('@nginstack/engine/lib/xml/XMLStreamReader');
const example = '<?xml version="1.0"?><notes><note>Note A</note><note>Note B</note></notes>';
const fileName = File.getTempFileName();
File.fileFromString(fileName, example);
const reader = XMLStreamReader.parseFile(fileName);
if (!reader.readNextStartElement() || reader.name !== 'notes') {
reader.raiseError('Não foi encontrada a tag "notes".');
}
const notes = [];
while (!reader.atEnd && reader.readNextStartElement()) {
if (reader.name === 'note') {
notes.push(reader.readElementText());
} else {
reader.raiseError('Elemento com tag "' + reader.name + '" não esperado.');
}
}
if (reader.hasError) {
throw new Error('Erro ao processar o arquivo XML: ' + reader.errorString);
}
reader.close();
Os dados também podem ser injetados no XMLStreamReader sob demanda, sendo este uso mais adequado para o processamento online de requisições HTTP:
const XMLStreamReader = require('@nginstack/engine/lib/xml/XMLStreamReader');
const part1 = "<xml><head>Hello</head><bo";
const part2 = "dy>World</body></xml>";
const reader = new XMLStreamReader(part1);
reader.readNextStartElement(); // => true
reader.readNextStartElement(); // => true
reader.name; // => "head"
reader.readElementText(); // => "Hello"
if (reader.readNext() === XMLStreamReader.TokenType.Invalid &&
reader.atEnd &&
reader.error === XMLStreamReader.ReadError.PrematureEndOfDocumentError
) {
reader.addData(part2);
}
reader.readNextStartElement(); // => true
reader.name; // => "body"
Nova API para obter valores de campos
Nas classes DBKey e DataSet, foram criados os métodos val()
, str()
, num()
, date()
,
bool()
e dbkey()
com o objetivo de obter o valor de um campo a partir de uma expressão lookup.
Exemplo de uso:
ds.str('pessoa.uf.regiao.nome'); // equivalente a ds.pessoa.uf.regiao.nome.
DBKey.from(-1898186559).date('iLastPasswdChg'); // equivalente a (-1898186559).ilastpasswdchg
Atualmente os valores dos campos de um DataSet ou de uma chave representada por um Number podem ser obtidos como propriedades. Essa abordagem, apesar de conveniente, tem os seguintes pontos negativos:
- Ela é incompatível com outras engines JavaScript como o V8, pois o primitivo Number não pode ser estendido de forma similar.
- IDEs não têm como presumir o tipo do dado retornado, diminuindo a eficácia das validações estáticas.
- Ao resolver uma expressão como
ds.pessoa.uf.regiao.nome
são criados objetos temporários desnecessários para representar os valores dos campos intermediários. Esses objetos aumentam a necessidade de execução do Garbage Collector do JavaScript, prejudicando o desempenho de processos que façam um uso intenso desse recurso.
Uma outra vantagem da nova API é o tratamento de campos nulos. Ao utilizar os métodos
str()
, num()
e bool()
, os valores retornados são normalizados para os primitivos equivalentes
''
, 0
e false
, simplificando as lógicas que fazem uso desses valores. Essa melhoria é mais
relevante no uso do V8, onde String(null)
é 'null'
em vez do valor ''
retornado pelo
JavaScript atual do Engine.
Ver DBKey.val() e DataSet.val() para mais detalhes e exemplos de uso.
Pacotes JAZ com escopo
Os pacotes JAZ passam a suportar escopos (@scope/package-name) nos seus nomes e os pacotes JAZ da plataforma passam a adotar o escopo @nginstack:
- erp-engine => @nginstack/engine
- erp-framework => @nginstack/web-framework
Essa modificação impacta códigos que incluem módulos JavaScript contidos nesses pacotes. Lembramos que códigos da VFS não devem fazer uso do require() para incluir módulos, pois mudanças no caminho dos módulos ainda podem ocorrer, afetando o funcionamento do sistema. Via de regra, códigos na Virtual File System sempre devem incluir bibliotecas por meio de chaves.
Nova numeração de versão da plataforma
Para garantir compatibilidade com os códigos existentes, a versão inicial seguirá a sequência
da numeração até então adotada, mas passará adotar uma política de
numeração mais próxima do Semantic Version. Em vez do padrão ano.mês.build
, será
liberado um incremento no major version a cada liberação de uma nova versão estável, inicialmente
em um ciclo mensal. Portanto, a próxima versão da plataforma será a 20.x.y, em vez de 19.1.y.
Além de adotar um racional mais moderno, essa mudança tem o objetivo de desassociar a versão da plataforma do produto desenvolvido em cima dela, dando destaque ao último. Por exemplo, o sistema de gestão construído sobre a plataforma poderá ter a versão 2019.1 e usar o Engine na versão 21. Para o cliente usuário do sistema, o que deverá importar é a versão do sistema e não a da plataforma.
Para manter consistência com os pacotes JAZ e documentação, a versão do executável do Engine
também passa a adotar uma numeração de 3 valores (major.minor.patch) em vez dos 4 números atuais.
Essa alteração se aplica inclusive na API JavaScript, mais especificamente nas propriedades
engine.version
, server.version
e connection.serverVersion
.
Site de documentação da plataforma
A documentação técnica da plataforma passa a estar disponível em https://nginstack.com. A documentação de referência em JSDoc pode ser consultada na opção API do menu superior da página inicial do novo site.
Ainda há pouco material disponível no site, pois a documentação antiga da plataforma precisa ser revista para se adequar ao novo formato do site. Essa revisão e migração deverá ocorrer de forma gradual nos próximos meses, principalmente sob a demanda de documentação das melhorias realizadas. Entre em contato com o suporte da plataforma para priorizar algum manual específico que ainda não tenha sido disponibilizado.
Melhorias
Admin
- Foi criado o processo
Admin > Licenciamento > Informações Legais
com o objetivo de dar detalhes sobre as licenças de projetos de código aberto utilizados pelo sistema, a fim de cumprir eventuais obrigações legais das empresas usuárias do sistema. As mesmas informações também estão disponíveis no Manage e na opção /About do ícone do Engine.
Desenvolvimento
- Extensões de produtos (licenças) passam a poder ser criadas na base desenvolvedora do produto. Antes apenas os desenvolvedores do produto Engine poderiam criar extensões de produtos.
- A execução dos testes unitários passa a ocorrer em um ambiente mais isolado das dependências incluídas pela própria biblioteca JSUnit, permitindo detectar melhor se todas as dependências do objeto em testes foram declaradas corretamente. Um efeito colateral dessa melhoria é que alguns testes unitários que antes eram bem sucedidos passarão a falhar. Eles devem ser revistos para incluir corretamente as suas dependências.
Engine
- A IDE passa a validar se arquivos JSON (extensão .json) estão sintaticamente corretos antes de gravar as alterações.
- Criados os métodos engine.exit() e engine.restart() para permitir a finalização e o reinício do Engine.
- As notificações do Engine passam a ser integradas ao Windows, permitindo que elas sejam exibidas na área de notificações e possam ser bloqueadas.
- Criada opção “Debugger” na IDE com o objetivo de exibir a janela do depurador JavaScript. Antes era necessário provocar uma parada do debugger na guia iDBCSql para configurar breakpoints por nome de função.
- A classe ScriptRunner passa a permitir indicar o runtime JavaScript a ser utilizado, podendo ser configurado o V8 em vez do runtime JavaScript padrão do Engine. Mais detalhes na documentação da classe ScriptRunner.
- O Engine passa a suportar a flag
--CloseAfterDBCacheSync
na linha de comando a fim de indicar que o sistema deve ser finalizado imediatamente após a primeira sincronização do cache local. O objetivo dessa nova flag é permitir a automatização de processos de preparação e validação do cache local. - Os eventos de autenticação de usuários via conexão a partir de bases externas passam a ser registrados na iLog com os eventos específicos “Acesso ao sistema (base externa)” e “Falha no acesso (base externa)”.
- Criada as propriedades
vendor.legalName
,vendor.logoUrl
evendor.logoTitle
com o objetivo de configurar as informações sobre o fornecedor do sistema. Códigos da plataforma que antes definiam essas informações de forma fixa no código fonte passa a observar essas novas propriedades. Mais detalhes em vendor. - Foram criados campos na Virtual File System para indicar a hora da criação e modificação dos arquivos. Antes o Engine guardava apenas a data de modificação.
Web Framework
- Foi criada a propriedade
SimpleLayout.defaults
com a finalidade de permitir que um fornecedor do sistema possa sobrepor as configurações padrões do SimpleLayout, como o nome da empresa licenciada apresentado nos relatórios. Mais detalhes em SimpleLayout.defaults. - A biblioteca JavaScript client-side do Web Framework e a imagem de fundo do login padrão foram otimizadas com o objetivo de reduzir o tamanho dos arquivos transferidos. Houve uma redução de 1.1 Mb na transferência de dados durante o fluxo de login do sistema quando não esses arquivos não estão no cache do navegador.
Defeitos corrigidos
Desenvolvimento
- Ao realizar uma atualização de sistema sempre eram gerados alertas no final da execução quando havia scripts de pós-upgrade na base destino que não existiam na origem. Esses alertas eram indevidos, pois esse é um cenário de uso correto e comum quando há uma base de desenvolvimento com um produto base e outras bases de desenvolvimento com produtos especializados.
- Ao remover os produtos não licenciados durante uma atualização do sistema, ocorria uma consulta adicional indevida de todos os registros negativos em todas as tabelas do sistema.
- Registros na iLog sobre a execução de atualização do sistema eram gravados com tags HTML, dificultando o seu uso fora do contexto do Web Framework.
- Atualização de sistema falhava com o erro “A chave XXXX foi alterada por outro usuário” caso houvesse uma permissão órfã com herança na base de dados destino.
- A atualização do sistema atualizava indevidamente os valores dos campos iRebuildVersion e iAggTableVersion da tabela iSchemaVersion com os valores existentes na base de dados de origem. Caso eles estivessem sendo utilizados na base destino da atualização, poderia ocorrer o descarte de tabelas do cache local ou uma falha na manutenção das tabelas de soma.
- A visualização de diferenças ou mesclagem de códigos não funcionava no processo Atualizar VFS caso fosse utilizado o WinMerge 2.15 ou superior.
- A atualização do Engine por meio dos processos Atualizar Engine ou Atualizar Sistema era incompleta quando o processo era executado em um Engine diferente do servidor de origem. Esse cenário de uso é importante para atender a necessidade de atualizar bases de dados inacessíveis a partir do servidor de origem, como bases de dados que não estão disponíveis na Internet.
- O protocolo HTTP sobre IAP falhava caso a versão do Engine não fosse composta por 4 números.
Engine
- Em determinadas situações, operações de ApplyUpdates com muitos registros poderiam provocam o erro “Stream Read Error” mesmo quando não havia uma falha de rede.
- Os scripts de inicialização do Engine não eram executados em ordem alfabética, convenção adotada nos demais tipos de scripts de inicialização.
- O método
require.resolve()
falhava caso fosse informada uma chave da Virtual File System. Esse defeito impedia o descarte do cache das classes Controller das rotas HTTP modificadas na Virtual File System. - Ao recriar um produto do sistema, a faixa de chaves anterior era aproveitada indevidamente caso houvesse uma expansão na quantidade de chaves negativas reservadas.
- O highlight de códigos JavaScript na IDE era apresentado com erros caso as aspas de encerramento de uma string de múltiplas linhas estivesse na primeira posição da linha.
- Queries SQL eram agregadas no profiler caso estivessem no mesmo nível de processamento, permitindo que queries distintas fossem totalizadas e apresentadas como se fossem a mesma expressão SQL.
- Em determinadas situações, o ícone do Engine sumia da barra de tarefas do Windows, exigindo que o desenvolvedor reiniciasse o Engine para que ele voltasse a ser exibido.
- Ao acessar um Engine utilizando um nome em vez um endereço IP, o sistema fazia um cache da resolução DNS internamente e o mantinha indefinidamente. Se o registro DNS fosse alterado para um novo IP, o Engine não detectava a alteração enquanto não fosse reiniciado. Agora o Engine passa a confiar no cache de DNS do sistema operacional e mantém um cache interno por no máximo 60 segundos.
- Ao executar o método
connection.executeScript
eram criadas indevidamente tarefas do Scheduler para registrar o login e logout do usuário, por mais que o usuário não tivesse feito um. Em alguns cenários de uso do sistema, essas tarefas poderiam sobrecarregar o Scheduler a ponto de impedir que ele executasse as demais tarefas contidas nele. - Ao utilizar a função
require()
em uma sessão JavaScript com o runtime V8 ocorria o erro “Access Violation”. - As classes nativas do Engine não eram acessíveis no runtime V8 por meio dos seus alias. Por exemplo, não era possível utilizar o alias FileStream da classe File, impedindo o seu uso em códigos que ainda usam o nome antigo.
- Ao utilizar os métodos
response.redirect
eresponse.stopAndRedirect
ocorria um downgrade do de segurança do protocolo HTTPS para o HTTP caso o HTTPS fosse implementado por um balanceador de carga em vez do Engine. - A propriedade pública
connection.remoteInstanceId
não estava documentada. - O método
keysUtilities.getUrl
prefixava a URL retornada com “/Raiz” em vez de “/”. - Ao ser persistido, pelo Engine 32 bits, um DataSet com campos number, tendo o conteúdo de dois registros consecutivos valores muito próximos, mas não idênticos, ocorria eventualmente de o campo do segundo registro ficar nulo, quando esse DataSet era restaurado no Engine 64 bits.
REST Framework
- O método
EntitySet.bindDataSet
poderia ignorar a atribuição de um novo DataSet caso ele possuísse um esquema similar ao anteriormente definido.
Web Framework
- Ao enviar um relatório por e-mail as colunas não eram formatadas corretamente, gerando casos em que não havia espaçamento separando as colunas.
Outras alterações
Desenvolvimento
- A senha mestre de um produto era limitada em 25 caracteres. O limite agora passa a ser 50.
- No processo Atualização do Sistema, todas as alterações de colunas passam a ser sugeridas. Antes, apenas as colunas sem conteúdo eram sugeridas. Caso a conversão não seja possível, será gerado um erro do banco de dados, comportamento que já ocorria na versão anterior. Na tela inicial, todos os produtos licenciados também passam a ser sugeridos, favorecendo o fluxo recomendado de evitar atualizações parciais.
- Foi eliminada a opção de executar os testes unitários nos processos de atualização. Essa opção tinha sido criada para atender o fluxo de geração de build do sistema, mas não era mais utilizada. O seu uso pelo cliente final não traz benefícios e pode gerar uma sobrecarga em bases do fornecedor do sistema.
- A atualização de sistema passa a definir a restrição “NOT NULL” nos campos iClass, CLASSE, iVersion e VERSAO caso eles não a tenham. A ausência dessa restrição afeta o desempenho de diversas consultas do sistema e sua ausência normalmente é decorrência de uma falha na criação da base de dados. Eventuais valores nulos nos campos de classe serão preenchidos com a chave da classe que definiu a tabela, enquanto os campos de versão serão preenchidos com o valor “1”.
Engine
- Engine e suas dependências passam a utilizar o Visual C++ 2017 runtime.
- V8 atualizado para a versão 6.9.
- Foi criada a interface
com.nginstack.engine.enginelet.Enginelet
que as classes Java devem implementar para serem acessadas a partir da API Enginelet. Foi preservada a classe anteriorbr.com.inteq.engine.enginelet.Enginelet
para fins de compatibilidade. - O tempo de inatividade máximo de conexões abertas pelo Engine foi alterado de 15 para 5 minutos com o objetivo de reduzir os erros de desconexões forçadas por firewalls. O tempo de 5 minutos foi estabelecido com base no adotado pelos principais navegadores Web do mercado.
Web Framework
- O sistema deixa de dar suporte ao navegador Internet Explorer 11. Os navegadores recomendados para uso do sistema continuam sendo o Google Chrome e o Firefox, em suas últimas versões estáveis.
- Apesar dos campos iOrder e iLanguage da iVfs serem legados, eles ainda eram referenciados por alguns códigos fontes da plataforma. Esses códigos foram revistos para não fazer mais uso desses campos e sugerimos que os desenvolvedores da plataforma eliminem as referências a eles. Eles deverão ser removidos em uma versão futura do sistema, pois são inconsistentes com integrações da Virtual File System com outros editores de código e sistemas de arquivos.