O Efeito Dunning-Kruger

A Internet é cheia de especialistas em tudo: parece que todos têm necessidade de dar suas opiniões sobre os mais diversos assuntos, desde política até futebol. Tenho certeza que você tem um parente mais velho que está muito convicto de que não precisa tomar determinado remédio à despeito da prescrição médica. Com certeza o seu tio apaixonado pelo Corinthians sabe que se tivessem substituído o jogador X aos 16’34” do segundo tempo, tudo teria sido diferente.

Também é muito provável que seu diretor, que nunca desenvolveu uma linha de código, comece a questionar suas estimativas e a qualidade do seu código depois de assistir uma palestra genérica sobre transformação digital. Essa confiança que as pessoas pouco informadas possuem e que as impede de enxergar o conhecimento dos outros e a própria ignorância é chamada de efeito Dunning-Kruger.

O efeito Dunning-Kruger é um viés cognitivo (padrão de distorção de julgamento) identificado em 1999 no artigo Unskilled and Unaware of It: How Difficulties in Recognizing One’s Own Incompetence Lead to Inflated Self-Assessments pelos psicólogos David Dunning e Justin Kruger da Cornell University. Esse viés é apresentado por indivíduos incompetentes em determinada área que acreditam serem mais capacitados que os especialistas daquele campo. O que motivou o trabalho dos pesquisadores foi descobrir o que levava as pessoas a tomarem más decisões quando persistiam em utilizar um conhecimento raso ou inexistente. Os pesquisadores descobriram que o senso de superioridade que as pessoas que adquirem uma nova informação apresentam cria uma barreira cognitiva que as impede de entender que na verdade são incompetentes, dificulta a administração do impacto negativo de suas decisões e também impede que elas reconheçam a competência dos verdadeiros especialistas.

O gráfico acima mostra uma relação entre a confiança e a quantidade de conhecimento que as pessoas têm. Como você pode observar, pouca informação ou competência tende a inflar o ego, mas quanto mais informação se adquire, mais humilde e consciente da baixa competência uma pessoa se torna até que ocorre uma nova inflexão ascendente no ponto em que a pessoa se torna um verdadeiro especialista e tem consciência disso. Ou seja, pessoas incompetentes tendem a ser muito seguras de si e superestimam suas habilidades, mas as pessoas competentes têm dúvidas e normalmente subestimam o que sabem. Acho que o mais curioso do gráfico não é a inflexão após acúmulo de conhecimento, mas sim o ponto máximo de confiança sem lastro em conhecimento equivalente. É nesse momento que a pessoa pode estar passando vergonha sem perceber e também é quando ela comete os maiores erros.

Vamos fazer uma analogia. Imagine que você está sentado na praia observando a vastidão do mar. Lá do seco, você pode achar que sabe muita coisa sobre o mar apenas observando o que está acima da água. Aí você faz selfs e posta nas redes sociais, mas, é claro, não sem antes escrever um texto sobre a fauna marinha e fazer reflexões poéticas sobre a brevidade da vida e a vastidão do oceano. Detalhe: você ainda nem molhou os pés. Em algum momento você resolve entrar na água e percebe que quanto mais você avança mar adentro mais toma consciência da profundidade das águas. Enquanto isso, seu amigo que ficou lá na areia está te criticando por ter ido tão longe e está querendo te dar dicas de onde pisar e para que lado ir. Ou seja, quanto mais você conhece, mais tende a achar que seu conhecimento é limitado – “só sei que nada sei”.

Para encerrar a primeira parte do artigo, te convido a apreciar o trabalho da simpática Cecilia Giménez, aspirante a restauradora, que “restaurou” a obra Ecce Homo, pintada pelo artista Elías García Martínez em um dos muros da igreja do santuário espanhol de Nossa Senhora da Misericórdia de Borja (Zaragoza) no início do século XIX. A obra era pouco conhecida, mas ficou famosa mundialmente depois que a velinha mostrou seus dotes artísticos. O pior é que ela se achou no direito de cobrar pela autoria da obra.

O Suco de Invisibilidade

Em 19 de abril de 1995, McArthur Wheeler decidiu assaltar dois bancos em plena luz do dia na cidade de Pittsburg no estado da Pensilvânia, Estados Unidos. O assaltante portava uma arma, mas diferente do nosso esteriótipo preferido dos ladrões de banco, ele não estava disfarçado, não cobriu o rosto e sequer tentou se esconder das câmeras de segurança:

Com as imagens das câmeras de segurança, o assaltante foi identificado e preso em pouco menos de uma hora. Ele estava visivelmente confuso e não compreendia o motivo de ter sido descoberto, mas sua reação não foi menor que a surpresa e as gargalhadas dos policiais quando ele alegou que estava coberto de suco de limão e por isso não deveria ter sido identificado.

Aparentemente, alguém disse ao senhor Wheeler que suco de limão funcionaria como uma espécie de tinta invisível. É verdade que se você escrever em um papel com suco de limão, esperar secar e aquecer o papel, a escrita será revelada. Esse efeito interessante foi demonstrado no filme A Lenda do Tesouro Perdido, de 2004, quando um dos “maiores atores do mundo”, Nicolas Cage, utilizou suco de limão e calor para revelar informações escondidas no verso da Declaração de Independência dos Estados Unidos pelos Pais Fundadores – essas e outras coisas apresentadas no filme nos deixam com a impressão de que essas figuras históricas tinham bastante tempo livre para ficar criando enigmas para esconder tesouros. Wheeler achou que suco de limão passado sobre o rosto lhe daria o poder da invisibilidade nos moldes do episódio O Verniz Invisibilizador, do Chapolin Colorado. É bem provável que já na cadeia ele tenha chegado a conclusão de que o efeito do suco de limão passou por causa do calor do seu próprio rosto.

Sabendo que toda teoria deve ser testada, o senhor Wheeler esfregou suco de limão no rosto e tirou uma foto de si mesmo com uma câmera Polaroid. Por algum motivo, o rosto dele não apareceu na foto e ele ficou confiante para cometer os assaltos. Na cadeia, após alguns testes, os policiais concluíram que o senhor Wheeler não era louco e nem estava sob efeito de entorpecentes: ele realmente acreditava na história que contou!

A história chamou a atenção de David Dunning, psicólogo da Universidade Cornell. Com a ajuda de seu aluno de pós-graduação, Justin Kruger, eles tentaram explicar como uma ideia absurda poderia trazer tanta confiança. A conclusão do estudo é que a maioria de nós tende a considerar nossas habilidades em determinadas áreas acima da média – mesmo sem conhecer a média – e melhores do que elas realmente são – ilusão de confiança. A confiança que temos em nosso conhecimento nem sempre está relacionada à quantidade ou qualidade do conhecimento que possuímos.

O Estudo

Em 1999, motivados pela curiosa história do “ladrão invisível”, Dunning e Kruger pediram que seus próprios alunos da Universidade de Cornell respondessem a um questionário com perguntas sobre gramática, lógica e humor. Eles pediram que os alunos avaliassem a pontuação geral que achavam que iriam obter e como suas pontuações se relacionavam com as pontuações dos demais participantes. Os alunos com pontuação mais baixa superestimaram suas próprias habilidades nos temas respondidos. Os 25% que obtiveram as notas mais baixas achavam que estavam acima da média – achavam que estavam entre os 33% que obtiveram as melhores pontuações.

A conclusão do estudo é que os alunos menos competentes sentiam-se tão competentes que eram incapazes de reconhecer a própria incompetência. Os pesquisadores também observaram que esses mesmos alunos tendiam a não reconhecer a competência das outras pessoas mesmo que essas pessoas tivessem reconhecidamente mais conhecimento naquelas áreas. Essa conclusão explica porque uma pessoa incompetente fica surpresa e até ofendida quando descobre que estava errada – ela tinha certeza que estava certa.

Um estudo relacionado realizado pelos mesmo autores em um clube de tiro esportivo mostrou resultados semelhantes. Dunning e Kruger usaram uma metodologia semelhante, fazendo perguntas aos aficionados sobre segurança de armas, visando que estes estimassem a si próprios sobre seus desempenhos no teste. Aqueles que responderam o menor número de perguntas de forma correta também superestimaram o conhecimento sobre armas de fogo. Veja mais algumas dados sobre supervalorização de conhecimento:

  • Em 2008, o mundo foi atingido por uma crise financeira que teve seu início nos Estados Unidos devido a esquemas arriscados desenvolvidos por indivíduos do setor financeiro e o desconhecimento dos consumidores. No ano de 2012, foi realizada uma pesquisa em que 23% das pessoas que haviam declarado falência nesse período acreditavam que eram especialistas em finanças.
  • Um estudo com engenheiros de software de duas grandes companhias mostrou que 32% dos engenheiros em uma empresa e 42% dos engenheiros na outra consideravam-se entre os 5% de profissionais mais competentes. Isso é simplesmente impossível, matematicamente falando.
  • Uma pesquisa revelou que 80% dos motoristas norte-americanos acreditam que dirigem acima da média. Também é assim que as pessoas tendem a avaliar a própria popularidade.

Minimizando o Efeito Dunning-Kruger

Todos estamos sujeitos ao viés previsto pelo estudo de Dunning e Kruger. Por isso, devemos tomar muito cuidado ao opinar sobre finanças, política e quaisquer outros assuntos dos quais não somos especialistas, pois da mesma forma que somos especialistas em determinadas áreas, somo totais incompetentes em outras.

Muito do que achamos que sabemos é apenas senso comum e preconceito. A dica mais importante para evitar o efeito é a humildade. A humildade é o primeiro passo que temos que dar para combater a ignorância que temos sobre nossa própria ignorância. Ela nos faz adotar uma postura receptiva frente ao conhecimento, mas sem deixar de lado o senso crítico. Assim, fica mais fácil ouvir e tentar entender o que os verdadeiros especialistas dizem e se aventurar a ler livros de diferentes áreas do conhecimento para depois cruzar esses conhecimentos e fazer algumas pequenas descobertas por si mesmo.

A ignorância gera confiança com mais freqüência que o conhecimento

Charles Darwin

A principal causa dos problemas no mundo de hoje é que os idiotas estão cheios de certeza enquanto as pessoas inteligentes estão cheias de dúvida.

Bertrand Russell, em O Triunfo da Estupidez

Todo mundo é ignorante, mas em assuntos diferentes.

Will Rogers

Referências

1. BATISTA, Julho. O efeito Dunning-Kruger, ou por que os ignorantes acham que são especialistas. Universo Racionalista. Disponível em: [https://universoracionalista.org/o-efeito-dunning-kruger-ou-por-que-os-ignorantes-acham-que-sao-especialistas/]. Acesso em 13 ago. 2020.

2. MAISRETORNO. Efeito Dunning-Kruger. Mais Retorno. Disponível em: [https://maisretorno.com/blog/termos/e/efeito-dunning-kruger]. Acesso em 13 ago. 2020.

3. EQUIPEIBC. VOCÊ SABE O QUE É EFEITO DUNNING KRUGER?. IBC – Instituto Brasileiro de Coaching. Disponível em: [https://www.ibccoaching.com.br/portal/comportamento/voce-sabe-que-efeito-dunning-kruger/]. Acesso em 13 ago. 2020.

4. TED-ED. Why incompetent people think they’re amazing – David Dunning. Ted-ed. Disponível em: [https://www.youtube.com/watch?v=pOLmD_WVY-E]. Acesso em 13 ago. 2020.

5. CARVALHO, Davi. Kotlin and MongoDB, a Perfect Match. Universo Racionalista. Disponível em: [https://universoracionalista.org/a-estupidez-dos-especialistas-de-internet-em-tempos-de-pandemia-o-efeito-dunning-kruger/]. Acesso em 13 ago. 2020.

Categorias:Filosofia

Como Alterar o Canal do Roteador

Se você mora em um prédio com o agravante de ter vários outros prédios em volta, o sinal do seu roteador vai oscilar bastantes por causa da colisão de pacotes oriunda do canal utilizado. Para verificar o canal que o seu roteador está utilizando e outras configurações dele, tem um aplicativo bem legal para Android: o Wifi Analyzer:

O Wifi Analyzer transforma seu celular em um analisador Wi-fi que exibe todos os canais que nos circundam. Na imagem acima, há vários roteadores ocupando os mesmos canais. Os canais acima do 11 estão livres. O ideal nesse caso seria modificar o canal do seu roteador para ocupar uma posição entre 12 e 14.

Uma vez que você esteja conectado ao roteador, basta acessar a interface administrativa dele no endereço 192.168.0.1 e informar o usuário e a senha. O problema é que esqueci a senha do roteador! Como me mudei e pedi uma nova instalação para o provedor Internet, suspeitei que havia me esquecido de alterar a senha padrão do roteador. Baixei um aplicativo chamado Senha do roteador padrão:

Esse aplicativo lista todos os usuários e senhas padrão de muitos fabricantes e modelos diferentes de roteador. Claro, você também poderia reiniciar as configurações do roteador para o padrão de fábrica, mas quis evitar a fadiga. Felizmente, eu não havia alterado a senha padrão. Então, acessei a interface administrativa e informei usuário e senha padrão:

Em seguida, alterei a senha do usuário admin e alterei o canal do roteador. Basta esperar alguns segundos para que o roteador consolide as alterações.

Por fim, acesse novamente o Wifi Analyzer e veja como está o sinal do seu roteador após a modificação do canal.

Piada: Churrasco Vs Cloud

Dá para usar uma analogia com churrasco para explicar o que você pode ter em uma cloud. Segundo um colega, tudo na vida pode ser SaaS:

Categorias:Geral

Logback: Introdução

O Logback é um framework de log que veio para substituir o Log4j e oferece mais opções de configuração e armazenamento de logs antigos.

A arquitetura do Logback é composta de três componentes: Logger, Appender e Layout:

Logger: contexto através do qual as aplicações interagem com o framework para criar mensagens de log;
Appender: local de destino de um log, como arquivo de texto e console. Cada Logger pode ter mais de um appender;
Layout: formato de saída das mensagens.

Como o Logback utiliza o SLF4J (Simple Logging Facade for Java SLF4J) como sua interface nativa, você deverá adicioná-lo como dependência no seu pom.xml junto com a dependência do logback:

<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-core</artifactId>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <scope>test</scope>
</dependency>

O logback-core é o módulo central do Logback que pode ser utilizado em combinação com outros módulos para os mais diferentes usos. Nesse artigo, vamos utilizar o suporte ao Logback que vem junto com o Spring Boot. Se você tem um projeto Spring Boot, não é necessário acrescentar mais nada no seu pom.xml. A configuração básica do log pode ser feita no seu arquivo application.properties e ela provavelmente atenderá às necessidades da aplicação:

logging.level.org.springframework.web=INFO
logging.level.org.hibernate=ERROR
logging.file=logs/my-app.log
logging.level.root=INFO
logging.pattern.console=&amp;amp;amp;amp;gt;%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%t] %mdc{tx-id:-no-tx-id} %-4line%-50C{10}%msg%n%throwable

Porém, se você precisa de logs mais granulares e detalhados, você precisará criar seu próprio xml de configuração do Logback no classpath e referenciá-lo no application.properties caso você esteja subindo a aplicação na sua IDE:

logging.config=classpath:logback.xml

Ao executar o JAR em um ambiente de produção, o próprio Spring Boot detecta a presença do arquivo de configuração de log no classpath. Para fazer um teste simples, anote sua class com @Log4j2 e logue alguma coisa:

@Log4j2
public class MyApplication implements CommandLineRunner {
   public static void main(String[] args) {
   SpringApplication.run(MyApplication .class, args);
   }

   @Override
   public void run(String... args) throws Exception {
      log.info("Aplicacao iniciada");
   }

}

Você verá algo assim no console e no arquivo de log. Definiremos essas configurações do arquivo de log á seguir.

2020-05-07 16:00:00.957 INFO  [restartedMain] no-tx-id 28   b.c.c.a.MyApplication  Aplicacao iniciada

Em nosso exemplo, o log é exibido no console e em um arquivo de texto. O interessante é que quando o log atingir um valor máximo, será criado um novo arquivo para manter o histórico:

%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%t] %mdc{tx-id:-no-tx-id} %-4line %-50C{10}%msg%n%throwable

${LOG_DIR}/my-app.log

%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%t] %mdc{tx-id:-no-tx-id} %-4line %-50C{10} %msg%n%throwable

&amp;amp;amp;lt;!-- rollover daily --&amp;amp;amp;gt;
${LOG_DIR}/my-app.%d{yyyy-MM-dd}.%i.log

10MB

No XML acima, destaco alguns elementos da configuração do padrão dos appenders, mas dá pra fazer muita configuração sobre o formato do log:

%d: utiliza SimpleDateFormat para formatar a apresentação da data e horário do log
-5level: nível do log
%msg: mensagem de log

O nível do log depende do que você precisa registrar. Todos os logs cujos níveis coincidam ou sejam superiores ao nível definido são registrados. Se o nível de log é INFO, todos os logs dos níveis INFO, WARN e ERROR serão registrados. Se o nível configurado é ERROR, apenas logs do nível ERROR serão registrados. Os níveis de log disponíveis são OFF (nada será logado), ERROR, WARN, INFO, DEBUG e TRACE.

Frequentemente, não queremos apenas que nossos logs sejam todos jogados no console ou inseridos em um mesmo arquivo que crescerá indefinidamente. Nós queremos que o arquivo seja particionado com base em alguns critérios, como tempo, tamanho ou ambos. O RollingFileAppender salva os logs em diferentes arquivos com base na rolling policy. TimeBasedRollingPolicy criará um novo arquivo em função da data – a mudança do dia força a criação de um novo arquivo. Quando o maxFileSize atingir o limite estabelecido, o arquivo será particionado de acordo com fileNamePattern. Nosso arquivo de log se chama:

my-app.log

Enquanto o arquivo não atingir o limite de tamanho de 10MB que estabelecemos, os logs continuarão a ser inseridos ali. O padrão do nome do nosso arquivo é:

my-app.%d{yyyy-MM-dd}.%i.log

Quando o arquivo my-app.log atinge 10MB, um novo arquivo é criado de acordo com o padrão onde %i é um incremento numérico sequencial:

my-app.2020-07-05.1.log
my-app.2020-07-05.2.log
my-app.2020-07-05.3.log
(...)

Quando o dia for alterado, my-app.log será renomeado para:

my-app.2020-07-06.log
(...)

Referências

1. BAELDUNG. A Guide To Logback. Baeldung. Disponível em: [https://www.baeldung.com/logback]. Acesso em 13 ago. 2020.

2. JT. Using Logback with Spring Boot. Spring Framework Guru. Disponível em: [https://springframework.guru/using-logback-spring-boot/]. Acesso em 13 ago. 2020.

3. NEWTON, Dan. Configuring Logback With Spring Boot. DZone. Disponível em: [https://dzone.com/articles/configuring-logback-with-spring-boot]. Acesso em 13 ago. 2020.

Categorias:Programação

Spring Boot Profiles

Conectar os profiles do Maven aos profiles do Spring Boot dá bastante flexibilidade ao deploy: você pode ter propriedades e configurações específicas para cada ambiente através de profiles. Nesse artigo, veremos como fazer essa sincronização:

O arquivo de propriedades padrão do Spring Boot tem esse nome:

application.properties

Quando uma aplicação Spring Boot é iniciada, o framework sempre procurará aquele arquivo. Isso significa que para ter flexibilidade nas configurações da aplicação para o propósito de desenvolvimento ou de instalação em um ambiente de produção, será necessário externalizar essa propriedade e alterar as configurações conforme as necessidades. Se você tiver propriedades específicas em função do ambiente, pode criar o arquivo de propriedades com esse padrão de nomenclatura:

application-{profile}.properties

Definiremos três arquivos de propriedades diferentes – um para cada ambiente:

application-dev.properties
application-hmg.properties
application-prod.properties

Em seguida, defina os profiles de sua aplicação no pom.xml:

<profiles>
   <profile>
      <id>dev</id>
      <properties>
	 <activatedProperties>dev</activatedProperties>
      </properties>
   </profile>
   <profile>
      <id>hmg</id>
      <properties>
         <activatedProperties>hmg</activatedProperties>
      </properties>
   </profile>
   <profile>
      <id>prod</id>
      <properties>
         <activatedProperties>prod</activatedProperties>
      </properties>
   </profile>
</profiles>

Agora precisamos transferir o controle do mapeamento de profiles para o Maven. No seu arquivo application.properties, adicione a propriedade abaixo:

spring.profiles.active=@activatedProperties@

É essa propriedade que define qual profile estará ativo (dev|hmg|prod). Para substituir essa propriedade dinamicamente durante a build, ative a filtragem do Resource Plugin do Maven:

<build>
   <resources>
      <resource>
         <directory>src/main/resources</directory>
         <filtering>true</filtering>
      </resource>
   </resources>
</build>

Para gerar um pacote para homologação, basta rodar assim:

mvn clean package -P hmg 

Para executar a aplicação em desenvolvimento:

mvn spring-boot:run -P dev

Ou, na sua IDE, cadastre esse propriedade:

spring.profiles.active=dev 

Uma Abordagem Ligeiramente Diferente

Você percebeu que para distribuir o jar da sua aplicação é necessário executar o mvn package com o profile específico de cada ambiente? Isso ocorre porque os arquivos de propriedade estão no classpath da sua aplicação. Embora isso não seja um problema se estiver utilizando o Jenkins, por exemplo, digamos que você queira que o mesmo jar, com o mesmo SHA-1, seja implantando em cada um dos ambientes.

Isso seria um problema, pois você forçou o profile do ambiente durante a compilação. Para resolver esse problema, basta que você distribua sua aplicação com todos os arquivos de propriedades de cada profile e indique, ao subir a aplicação, qual é o profile ativo e onde está, no path, o arquivo de propriedades daquele profile:

java -jar app*.jar --spring.profiles.active={dev|hmg|prod} --spring.config.location=application-{dev|hmg|prod}.properties > /dev/null &

Se esse arquivo não for encontrado, o Spring Boot tentará encontrar o arquivo application.proprierties.

Referências

1. OLSZEWSKI, Daniel. Activating Spring Boot profile with Maven profile. Dev in Web. Disponível em: [http://dolszewski.com/spring/spring-boot-properties-per-maven-profile/]. Acesso em 13 ago. 2020.

Categorias:Programação

Como Realizar um Mapeamento @OneToOne com @JoinTable

Vi uma situação curiosa um dia desses. Precisei fazer join de duas tabelas de um banco legado que eram muito parecidas: quando um registro era inserido em TBL_PESSOA, uma trigger era disparada e um registro com o mesmo identificador da pessoa era inserido em TBL_USUARIO. O interessante é que essas tabelas estavam em bancos diferentes. Eu estava conectado no banco que tinha a pessoa, mas precisava de um atributo do usuário.

Para atingir esse objetivo, utilizei @JoinTable em complemento com @OneToOne para fazer com que o usuário fosse um componente de pessoa:

@Entity
@Table(name = "TBL_PESSOA")
public class Pessoa{
   @Id
   @GeneratedValue(strategy = GenerationType.IDENTITY)
   @Column(name = "PK_PESSOA")
   private Long id;
   private String nome;
   @OneToOne
   @JoinTable(name = "TBL_USUARIO",
      joinColumns = {
         @JoinColumn(name = "PK_USUARIO")
      },
      inverseJoinColumns = {
         @JoinColumn(name = "PK_PESSOA")
      }
    )
    private Usuario usuario;
}

Referências

1. LOGICBIG. JPA – @JoinTable Examples. LOGICBIG.COM. Disponível em: [https://www.logicbig.com/how-to/code-snippets/jcode-jpa-jointable.html]. Acesso em 13 ago. 2020.

2. ______. JPA – Using @JoinTable in @OneToMany association. Zian’s Blog. Disponível em: [https://www.logicbig.com/tutorials/java-ee-tutorial/jpa/one-to-many-with-join-table.html]. Acesso em 13 ago. 2020.

Categorias:Programação

Thymeleaf: Criação de uma Modal Dinâmica

Nesse artigo, vamos ver como criar um diálogo dinâmico de exclusão de pessoas utilizando Thymeleaf e Bootstrap.

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css">
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js"></script>
</head>	

<body>
<div th:switch="${pessoas}">
  <h2 th:case="null">Nenhuma pessoa foi encontrada</h2>
    <div th:case="*" class="row justify-content-center">
      <div class="col-auto">
        <table class="table table-striped table-hover table-responsive">
        <thead class="thead-dark">
          <tr class="bg-primary">
            <th>Id</th>
            <th>Nome</th>
            <th colspan="1">Ações</th>
          </tr>
        </thead>
        <tbody>
          <tr th:each="pessoa : ${pessoas}">
            <td th:text="${pessoa.id}">
              <input th:field="${pessoa.id}" />
            </td>
            <td th:text="${pessoa.nome}">
              <input th:field="${pessoa.nome}" />
            </td>
            <td>
              <a th:href="@{/pessoa/editar/{id}(id=${pessoa.id})}" class="btn btn-info btn-sm">Editar</a>
            </td>
            <td>
              <button type="button" class="btn btn-danger btn-sm" data-toggle="modal" th:attr="data-target='#remover_'+${pessoa.id}" >Remover</button>
              <div class="modal fade" th:id="remover_+${pessoa.id}"  tabindex="-1" role="dialog" aria-hidden="true">
                <div class="modal-dialog modal-dialog-centered" role="document">
                  <div class="modal-content">
                    <div class="modal-header">
                      <h5 class="modal-title" id="exampleModalLongTitle">Confirmação</h5>
                      <button type="button" class="close" data-dismiss="modal" aria-label="Close">
                         <span aria-hidden="true">&times;</span>
                      </button>
                    </div>
                    <div class="modal-body">
                      <p>Tem certeza que quer remover a pessoa?</p>
                    </div>
                    <div class="modal-footer">
                      <button type="button" class="btn btn-secondary" data-dismiss="modal">Não
                      </button>
                      <a type="button" class="btn btn-primary" th:href="@{/pessoa/remover/{id}(id=${pessoa.id})}">Sim</a>
                    </div>
                  </div>
                </div>
              </div>
            </td>
          </tr>
        </tbody>
      </table>
    </div>
  </div>
</div>
</body>
</html>

Referências

1. KABIR, Zian. Bootstrap Dynamic Modal with Spring Boot and Thymeleaf. Zian’s Blog. Disponível em: [https://blog.nazrulkabir.com/2018/04/dynamic-modal-with-spring-boot-and-thymeleaf/]. Acesso em 13 ago. 2020.

2. STACKOVERFLOW. Thymeleaf – delete with confirme modal. StackOverflow. Disponível em: [https://stackoverflow.com/questions/49009822/thymeleaf-delete-with-confirme-modal]. Acesso em 13 ago. 2020.

Categorias:Programação

Uma Curiosa Questão de Lógica

Esse problema de lógica é simples, mas quase me pegou!

if
1 = 5
2 = 25
3 = 325
4 = 4325, then
5 = ?

A seqüência nos induz ao erro: somos levados a imaginar que 5 será igual a um número maior que 4325, porém, logo no início, foi estabelecido que:

1 = 5

Sendo assim, é válido afirmar que:

5 = 1

Categorias:Matemática

Como Extrair as Informações Pessoais de um Certificado Digital

Trabalhar com certificados digitais pode ser uma tarefa difífil. Para extrair uma determinada informação, é necessário conhecer o OID (Object Identifier) e a posição em que o valor corresponde ao OID deve ser lida. Para saber como interpretar uma determinada informação, você deve ler a política do certificado, que é fornecida publicamente pela AC, como a Valid, o Serasa e a Certisign.

Nesse artigo, vamos ver o básico que precisamos para extrair as informações pessoais de um e-CPF A3. Sugiro que você dê uma olhada em outro artigo em que trato brevemente de OIDs e do BouncyCastle. Exporte o .cer do seu certificado digital com o nome rfb.cer para que configuremos a cadeia permitida no HTTP Apache. No arquivo httpd-ssl.conf do HTTP Apache, configure um virtual host para o seu site:

<VirtualHost _default_:443>
   (...)
   SSLProtocol -all +TLSv1.2
   <Location /site>
      SSLVerifyClient require
      SSLVerifyDepth 5
      SSLRenegBufferSize 31457280
   </Location>
   SSLCACertificateFile "/conf/ssl.crt/rfb.cer"
</VirtualHost>

É necessário acessar a URL do seu site com https:// para que, via client authentication, o navegador entregue para sua aplicação, via Apache, o certificado digital informado pelo usuário. Para saber como interpretar os dados pessoais de um certificado e-CPF A3 da AC Valid, por exemplo, veja o item [a] da seção [7.1.2.3] da política deles. Para facilitar, vamos criar uma classe para representar uma pessoa física:

private class PessoaFisisca {
   String email;
   String tituloEleitor;
   String zona;
   String secao;
   String municipioEleitor;
   String ufEleitor;
   String dataNascimento;
   String cpf;
   String pis;
   String rg;
   String orgaoExpeditor;
   String uf;
   String nomeResponsavel;
   String inss;
}

Tentei deixar o código o mais simples possível até para que até desenvolvedores que não trabalham com Java possam entendê-lo e de alguma forma adaptá-lo para suas tecnologias. Evitei utilizar lambdas, streams e outras coisas que aumentariam desnecessariamente a complexidade do código embora eu tenda a utilizar o melhor que a linguagem tem a oferecer para desenvolver uma solução real.

Agora vamos passo a passo. O Apache irá entregar a cadeia de certificados digitais para sua aplicação no cabeçalho da request. Você deve extrair o X509Certificate[] da HttpServletRequest. Nesse array, precisamos identificar o certificado do usuário. Ao iterar pelo array, observe o atributo basicConstraints (OID 2.5.29.19) do certificado. Se o valor é -1, indica que o atributo pathLenConstraint, que representa a profundidade da cadeia de certificados, é de um certificado de usuário. Para valores positivos, o certificado pode ser de uma AC raiz ou intermediária.

private X509Certificate extrairCertificado(HttpServletRequest request) {
   X509Certificate[] certificates = (X509Certificate[]) 
   request.getAttribute("javax.servlet.request.X509Certificate");
   X509Certificate certificate = null;
   for (int ind = 0; certificates != null && ind < certificates.length; ind++) {
      X509Certificate cert = certificates[ind];
      if (cert.getBasicConstraints() == -1) {
         certificate = cert;
         break;
      }
   }
   return certificate;
}

As informações que queremos estão no atributo subjectAlternativeNames do certificado. Cada um desses elementos pode ser uma String ou um tipo complexo, que é interpretado como um byte[]. Sendo um byte[], precisamos utilizar a API do BouncyCastle para fazer o parse e extrair o OID e o valor para o qual aquele OID aponta:

private class OIDEncoded{
   String oid;
   String encoded;
}

private OIDEncoded parseOidEncoded(byte[] alternativeNameValue) throws Exception {
   ASN1InputStream in = new ASN1InputStream(new ByteArrayInputStream(alternativeNameValue));
   ContentInfo sigData = ContentInfo.getInstance(in.readObject());
   String oid = sigData.getContentType().getId();
   DERTaggedObject content = (DERTaggedObject) sigData.getContent();
   ByteArrayOutputStream bout = new ByteArrayOutputStream();
   DEROutputStream out = new DEROutputStream(bout);
   out.writeObject(content);
   String encoded = bout.toString();

   OIDEncoded oidEncoded = new OIDEncoded();
   oidEncoded.oid = oid;
   oidEncoded.encoded = encoded;

   return oidEncoded;
}

Com o objeto OIDEncoded que criamos, vamos extrair as informações de acordo com a política da AC. Comecemos pelo campo e-mail. De acordo com a especificação, o campo rfc822Name representa o e-mail do titular do certificado. Em Java, esse campo é do tipo String:

private void extrairEmail(PessoaFisisca pessoaFisica, Object alternativeNameValue) {
   if (alternativeNameValue instanceof String) {
      pessoaFisica.email = (String) alternativeNameValue;
   }
}

O OID 2.16.76.1.3.1 contém os dados pessoais da pessoa física para um certificado ICP. Basta verificar na especificação onde começa e onde termina cada campo.

private void extrairDadosPessoais(PessoaFisisca pessoaFisica, OIDEncoded oidEncoded) {
   if (oidEncoded.oid.equals("2.16.76.1.3.1")) {
      pessoaFisica.dataNascimento = oidEncoded.encoded.substring(4, 12);
      pessoaFisica.cpf = oidEncoded.encoded.substring(12, 23);
      pessoaFisica.pis = oidEncoded.encoded.substring(23, 34);
      pessoaFisica.rg = oidEncoded.encoded.substring(34, 49);
      pessoaFisica.orgaoExpeditor = oidEncoded.encoded.substring(49, oidEncoded.encoded.length() - 2);
      pessoaFisica.uf = oidEncoded.encoded.substring(oidEncoded.encoded.length() - 2);
   }
}

O OID 2.16.76.1.3.2 contém o nome do responsável pelo certificado:

private void extrairNomeResponsavel(PessoaFisisca pessoaFisica, OIDEncoded oidEncoded) {
   if (oidEncoded.oid.equals("2.16.76.1.3.2")) {
      pessoaFisica.nomeResponsavel = oidEncoded.encoded.substring(4);
   }
}

O OID 2.16.76.1.3.5 contém o título de eleitor. Basta verificar na especificação onde começa e onde termina cada campo.

private void extrairTituloEleitor(PessoaFisisca pessoaFisica, OIDEncoded oidEncoded) {
   if (oidEncoded.oid.equals("2.16.76.1.3.5") && !oidEncoded.encoded.substring(4,23).equals("0000000000000000000")) {
      pessoaFisica.tituloEleitor = oidEncoded.encoded.substring(4, 16);
      pessoaFisica.zona = oidEncoded.encoded.substring(16, 19);
      pessoaFisica.secao = oidEncoded.encoded.substring(19, 23);
      pessoaFisica.municipioEleitor = oidEncoded.encoded.substring(23, oidEncoded.encoded.length() - 2);
      pessoaFisica.ufEleitor = oidEncoded.encoded.substring(oidEncoded.encoded.length() - 2);
   }
}

O OID 2.16.76.1.3.6 contém o número do INSS:

private void extrairNumeroInss(PessoaFisisca pessoaFisica, OIDEncoded oidEncoded) {
   if (oidEncoded.oid.equals("2.16.76.1.3.6")) {
      pessoaFisica.inss = oidEncoded.encoded.substring(4);
   }
}

Por fim, juntemos tudo:

public void analisarCertificadoPessoaFisica(HttpServletRequest request) throws Exception {
   X509Certificate certificate = extrairCertificado(request);
   Collection alternativeNames = certificate.getSubjectAlternativeNames();
   PessoaFisisca pessoaFisica = new PessoaFisisca();
   for (Iterator i = alternativeNames.iterator(); i.hasNext();) {
      List alternativeName = (List) i.next();
      Object alternativeNameValue = alternativeName.get(1);
      extrairEmail(pessoaFisica, alternativeNameValue);
      if (alternativeNameValue instanceof byte[]) {
         OIDEncoded oidEncoded = parseOidEncoded((byte[]) alternativeNameValue);
         extrairDadosPessoais(pessoaFisica, oidEncoded);
         extrairNomeResponsavel(pessoaFisica, oidEncoded);
         extrairTituloEleitor(pessoaFisica, oidEncoded);
         extrairNumeroInss(pessoaFisica, oidEncoded);
      }
   }
}

Kotlin: Persistência com MongoDB

Nesse artigo, vamos utilizar Kotlin e Spring Data para nos conectar ao banco de dados pessoa que criamos em outro artigo. Em um primeiro exemplo, vamos expor as pessoas recuperadas do banco em um webservice Restful e em um segundo exemplo apresentaremos essas pessoas em uma interface web. Configure um projeto SpringBoot dessa forma:

O build.gradle.kts completo fica assim:

import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
   id("org.springframework.boot") version "2.3.0.RELEASE"
   id("io.spring.dependency-management") version "1.0.9.RELEASE"
   kotlin("jvm") version "1.3.72"
   kotlin("plugin.spring") version "1.3.72"
}
group = "br.com.kmongo"
version = "0.0.1-SNAPSHOT"
java.sourceCompatibility = JavaVersion.VERSION_1_8

repositories {
   mavenCentral()
}
dependencies {
   implementation("org.springframework.boot:spring-boot-starter-data-mongodb")
   implementation("org.springframework.boot:spring-boot-starter-web")
   implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
   implementation("org.jetbrains.kotlin:kotlin-reflect")
   implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
   developmentOnly("org.springframework.boot:spring-boot-devtools")
   testImplementation("org.springframework.boot:spring-boot-starter-test") {
      exclude(group = "org.junit.vintage", module = "junit-vintage-engine")
   }
}
tasks.withType<Test> {
   useJUnitPlatform()
}
tasks.withType<KotlinCompile> {
   kotlinOptions {
      freeCompilerArgs = listOf("-Xjsr305=strict")
         jvmTarget = "1.8"
   }
}

Configure as propriedades do banco de dados no arquivo application.properties:

server.port=8080
server.servlet.context-path=/
#mongodb
spring.data.mongodb.host=localhost
spring.data.mongodb.port=27018
spring.data.mongodb.database=pessoa
spring.data.mongodb.username=rodrigo
spring.data.mongodb.password=123456
#logging
logging.level.org.springframework.data=debug
logging.level.=error

Vamos nos basear no exemplo do Restful que fizemos no artigo Spring Boot: Persistência com MongoDB. Crie a entidade Pessoa:

@Document(collection = "pessoas")
class Pessoa ( @Id
   var _id: ObjectId,
   @Indexed
   @Field("nome")
   var nome: String
)

Crie a interface do repositório:

@Repository
interface PessoaRepository : MongoRepository<Pessoa, Long>

Vamos criar uma classe de serviço onde injetaremos nosso repositório e definiremos um contexto transacional. Esse contexto transacional não é necessário, mas coloquei apenas para deixar marcado onde costumo definir as transações:

@Service
class PessoaService{

    @Autowired
    private lateinit var repository : PessoaRepository

    @Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
    fun findAll() : List<Pessoa>{
        return repository.findAll();
    }
}

Crie o RestController para definir a API que exporá nosso modelo:

@RestController
@RequestMapping( "/pessoas")
class PessoaController {
    @Autowired
    private lateinit var service : PessoaService

    @GetMapping("/")
    fun buscarTodos() : ResponseEntity<List<Pessoa>> {
        return ResponseEntity.ok(service.findAll())
    }
}

Acessando o endereço:

http://localhost:8080/pessoas/

Você verá o seguinte JSON:

[
   {
      _id: {
         timestamp: 1589651018,
         date: "2020-05-16T17:43:38.000+00:00"
      },
      nome: "Pedro"
   },
   {
      _id: {
         timestamp: 1589651026,
         date: "2020-05-16T17:43:46.000+00:00"
      },
      nome: "Mario"
   },
   {
      _id: {
         timestamp: 1589651032,
         date: "2020-05-16T17:43:52.000+00:00"
      },
      nome: "Daniel"
   }
]

Exemplo 2: Mustache Template Engine

Nesse exemplo, reutilizaremos o serviço que criamos e utilizaremos a template engine Mustache para apresentar uma interface web simples. Adicione a dependência abaixo ao seu build.gradle.kts:

dependencies {
   (...)
   implementation("org.springframework.boot:spring-boot-starter-mustache")
}

Crie o controller abaixo. Você deve importar o pacote org.springframework.ui.set para poder atribuir um valor diretamente a um array:

import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Controller
import org.springframework.ui.Model
import org.springframework.ui.set
import org.springframework.web.bind.annotation.GetMapping

@Controller
class HtmlController {
  @Autowired
  private lateinit var service : PessoaService

  @GetMapping("/")
  fun lista(model: Model): String {
    model["pessoas"] = service.findAll()
    return "lista"
  }
}

Crie o arquivo \resources\templates\lista.mustache com esse conteúdo:

<html>
<body>
    {{#pessoas}}
        <h2>{{nome}}</h2>
    {{/pessoas}}
</body>
</html>

Ao acessar o endereço:

http://localhost:8080/

Você verá esses valores:

Pedro
Mario
Daniel

Referências

1. HAUER, Phillipp. Kotlin and MongoDB, a Perfect Match. Philipp Hauer’s Blog. Disponível em: [https://phauer.com/2018/kotlin-mongodb-perfect-match/]. Acesso em 13 ago. 2020.

2. ROBERT, David. API Persistente com Kotlin, Spring Boot, MySQL, JPA e Hibernate. Elo7. Disponível em: [https://elo7.dev/api-persistente-com-kotlin-springboot-mysql-jpa-hibernate/]. Acesso em 13 ago. 2020.

3. SPRING. Building web applications with Spring Boot and Kotlin. Spring. Disponível em: [https://spring.io/guides/tutorials/spring-boot-kotlin/]. Acesso em 13 ago. 2020.

Categorias:Programação