Archive

Archive for the ‘Programação’ Category

Como Habilitar o Ehcache no Shiro

O Shiro oferece um mecanismo de cache para diminuir o tempo de respostas das operações de segurança, mas as implementações disponibilizadas podem ser utilizadas para propósitos fora do escopo do framework de segurança.

Figura 1 – Arquitetura do Shiro

Várias implementações do CacheManager estão disponíveis. Nesse artigo, mostraremos como habilitar o EhCacheManager, o que permite utilizar o Ehcache. Antes de continuar, sugiro que você veja o artigo de introdução ao Shiro que escrevi e um outro artigo sobre a integração do Ehcache ao Spring, onde faço algumas considerações sobre o cache em si.

Comecemos pela configuração das dependências do Maven:

   <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-core</artifactId>
      <version>1.3.2</version>
   </dependency>
   <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-web</artifactId>
      <version>1.3.2</version>
    </dependency>
    <dependency>
      <groupId>org.ehcache</groupId>
      <artifactId>ehcache</artifactId>
      <version>3.3.1</version>
    </dependency>  
    <dependency>
      <groupId>javax.cache</groupId>
      <artifactId>cache-api</artifactId>
      <version>1.0.0</version>
    </dependency>

Em seguida, vamos configurar o arquivo shiro.ini

[main]
[main]
apprealm = br.com.app.autorizacao.AppRealm
securityManager.realms = $apprealm
cacheManager = org.apache.shiro.cache.ehcache.EhCacheManager
securityManager.cacheManager = $cacheManager

Uma vez definido um CacheManager, o Shiro o entrega para as classes que implementam a interface CacheManagerAware. Nosso AppRealm é um SimpleAccountRealm. Sendo assim, já estamos implementando aquela interface.

Referências

1. [https://shiro.apache.org/caching.html]
2. [https://shiro.apache.org/static/1.3.0/apidocs/org/apache/shiro/cache/Cache.html]
3. [https://shiro.apache.org/static/1.3.0/apidocs/org/apache/shiro/cache/ehcache/EhCacheManager.html]
4. [http://www.ehcache.org/]

Prova Computacional do Problema de Monty Hall

O Problema de Monty Hall, que outrora apresentei, é um jogo probabilístico em que um apresentar pede que uma dentre três portas seja escolhida. Uma porta contém um prêmio e as outras têm alguma bizarrice. A imagem abaixo resume as possibilidades de escolha depois que uma porta é aberta pelo apresentador:

Figura 1 – Síntese das escolhas

Para sermos mais didáticos, podemos utilizar a árvore de decisão abaixo, que mostra que temos 2/3 de chances de vencer trocando de porta:

Figura 2 – Árvore de decisão

O blog O Desafio: Aprender apresentou o problema de Monty Hall e citou minha explicação da solução. Revisitar o problema me fez pensar em ir um pouco além e desenvolver um pequeno algoritmo em Java para provar a solução já conhecida do problema.

Escrevi rapidamente um código que utilizava listas de strings e mapas de portas para totais de escolhas. Funcionou, mas depois gastei um tempo reescrevendo com orientação a objetos visando criar abstrações mais claras para que pudesse ser didático. Primeiro, criei uma classe que representa uma porta e o total de vezes que ela foi escolhida:


public class Porta {
    private String nome;
    private long totalEscolhas;
 
    public Porta(String nome) {
        this.nome = nome;
    }
 
    public void escolher() {
        totalEscolhas++;
    }
 
    public long getTotalEscolhas() {
        return totalEscolhas;
    }
 
    public String getNome() {
        return nome;
    }
};

Sabemos que o problema começa quando uma porta é escolhida e outra é aberta pelo apresentador. Sabemos também que a porta aberta e aquela que permanece fechada e que você não escolheu formam um grupo.

Figura 3 – Grupos de portas

Para o problema, isso significa que, se a porta aberta for escolhida pelo algoritmo de escolhas aleatórias, a escolha será transferida para a porta do mesmo grupo que permaneceu fechada. Pensando assim, criei uma abstração para a porta aberta que estende a porta comum e recebe uma referência para a porta fechada. Pensei no padrão Decorator:

public class PortaAberta extends Porta {
   private Porta portaDelegada;

   public PortaAberta(String nome, Porta portaDelegada) {
      super(nome);
      this.portaDelegada = portaDelegada;
   }

   public void escolher() {
      portaDelegada.escolher();
   }

};

O algoritmo que escrevi faz três coisas:

  1. Cria as portas fechadas e a porta aberta e as adiciona em uma lista;
  2. Escolhe aleatoriamente uma das portas e incrementa o total de escolhas em cada instância de porta;
  3. Exibe a porcentagem de vezes que cada porta foi escolhida.
public class MontyHall {
   private List<Porta> portas;

   public void executar(long totalEscolhas) {
      configurarPortas();
      escolher(totalEscolhas);
      exibir(totalEscolhas);
   }

   private void configurarPortas() {
      /*
      * Porta 1 do Grupo A (A1)
      */
      Porta portaA1 = new Porta("A1");
      /*
      * Porta 1 do Grupo B (B1)
      */
      Porta portaB1 = new Porta("B1");
      /*
      * Porta 2 do Grupo B (B2)
      * 
      * Essa e a porta que foi aberta pelo apresentador.
      * Se ela for escolhida pelo sistema, a escolha sera 
      * delegada para a outra porta do mesmo grupo, que e a porta B1
      */
      Porta portaB2 = new PortaAberta("B2", portaB1);
      portas = Arrays.asList(portaA1, portaB1, portaB2);
   }

   private void escolher(long totalEscolhas) {
      Random escolhaAleatoria = new Random();
      for (int escolha = 0; escolha < totalEscolhas; escolha++) {
         int indicePortaEscolhida = escolhaAleatoria.nextInt(portas.size());
         Porta porta = portas.get(indicePortaEscolhida);
         porta.escolher();
      }
   }
   private void exibir(long totalEscolhas) {
      System.out.println("\n" + totalEscolhas + " tentativas\n");
      NumberFormat totalEscolhasFormat = NumberFormat.getPercentInstance();
      totalEscolhasFormat.setMinimumFractionDigits(4);
      for (Porta porta : portas) {
        String totalPorta = totalEscolhasFormat.format(
           (double) porta.getTotalEscolhas() / totalEscolhas);
         System.out.println(porta.getNome() + ": " + totalPorta);
      }
   }
}

Agora, vamos testar nosso código para algumas configurações de quantidades de tentativas:

@RunWith(JUnit4.class)
public class TesteMontyHall {
   @Test
   public void teste() {
      MontyHall montyHall = new MontyHall();	
      montyHall.executar(10);
      montyHall.executar(100);
      montyHall.executar(1000);		
      montyHall.executar(1000000);
      montyHall.executar(1000000000);
   }
}

A saída do programa para as configurações anteriores mostra que quanto maior a quantidade de tentativas, mais evidente fica a tendência dos resultados:

10 tentativas
A1: 40,0000%
B1: 60,0000%
B2: 0,0000%

100 tentativas
A1: 38,0000%
B1: 62,0000%
B2: 0,0000%

1000 tentativas
A1: 33,7000%
B1: 66,3000%
B2: 0,0000%

1000000 tentativas
A1: 33,3255%
B1: 66,6745%
B2: 0,0000%

1000000000 tentativas
A1: 33,3360%
B1: 66,6640%
B2: 0,0000%

Esse algoritmo não funcionaria bem para mais de 3 portas. Eu modificaria a abstração da porta aberta: é necessário que ela receba todas as outras portas do grupo das portas não escolhidas e que estão fechadas e, quando a porta aberta for escolhida aleatoriamente, é necessário escolher aleatoriamente uma das portas desses grupo.

Como Adicionar um Filtro HTTPBasicAuthFilter em um Cliente Jersey

O HTTPBasicAuthFilter é uma classe utilitária do Jersey que adiciona um cabeçalho de autenticação HTTP se a request que está sendo montada ainda não o possuir:

Client client = Client.create();
client.addFilter(new HTTPBasicAuthFilter("usuario", "senha"));

Vamos modificar nosso exemplo de cliente Jersey para autenticação HTTP:

public Autorizacao autenticar() throws Exception  {
   Client client = Client.create();
   client.addFilter(new HTTPBasicAuthFilter("usuario", "senha"));

   JSONObject jsonInput = new JSONObject();
   jsonInput.put("usuario", "usuario");
   jsonInput.put("senha", "senha");

   WebResource target = client.
    resource("http://localhost:8080").
    path("/autenticar/");
   ClientResponse response = target.
    type(MediaType.APPLICATION_JSON).
    post(ClientResponse.class, jsonInput.toString());

   String value = response.readEntity(String.class);
   response.close();
   ObjectMapper map = new ObjectMapper();
   return map.readValue(value, Autorizacao.class);
}

public List<Usuario> listarUsuarios(Autorizacao autorizacao)
   throws Exception {
   Client client = Client.create();
   client.addFilter(new HTTPBasicAuthFilter("usuario", "senha"));

   WebResource target = client.
    resource("http://localhost:8080").
    path("/usuario/listar/");

   ClientResponse response = target.
      accept(MediaType.APPLICATION_JSON).
      header("Token", autorizacao.getToken()).get(ClientResponse.class);

   String value = response.readEntity(String.class);
   response.close();
   ObjectMapper map = new ObjectMapper();
   Map<String, List<Usuario>> mapItens = map.
     readValue(value,
      new TypeReference<Map<String, List<Usuario>>>() {
     });
   return mapItens.get("usuarios");
}
Categorias:Programação

Java 8 Collections

Os métodos padrão introduzidos no Java 8 permitiram que novas funcionalidades fossem agregada à API de Collections. Vamos ver um pouco do poder dessa API melhorada através de um exemplo. Suponha que você precisa descobrir que Pessoa em uma lista de pessoas tem a maior idade:

List<Pessoa> pessoas = new ArrayList<Pessoa>();
pessoas.add(new Pessoa("Mario", 83));
pessoas.add(new Pessoa("Rita", 70));
pessoas.add(new Pessoa("Maria", 50));
pessoas.add(new Pessoa("Ana", 48));
pessoas.add(new Pessoa("Tadeu", 43));

Sabendo que a lista de pessoas está em ordem decrescente de idade, podemos fazer simplesmente assim:

pessoas.get(0);

Ou, de forma mais elegante, podemos fazer um filtro:

pessoas.stream().filter(pessoa -> e.getIdade() == idade).findFirst().get();

Para complicar um pouco, vamos embaralhar a lista antes de fazer a pesquisa:

Collections.shuffle(pessoas);

Poderíamos organizar a lista com um Comparator de idade para garantir a ordem e depois utilizar um dos métodos anteriores para obter o primeiro item. Vamos modificar o problema para deixá-lo um pouco mais complexo.

Map<Sala, Integer> map = new HashMap<>();
pessoas.forEach(pessoa -> map.put(pessoa, idade));

Adicionei todas as pessoas em um Map e quero aquela que tem a maior idade. Supondo que não temos a lista de Pessoas original, você poderia sugerir que criássemos uma lista à partir do Map e a ordenássemos. É o que eu faria antigamente, mas no Java 8 dá pra fazer algo mais ineteressante com Stream:

Map.Entry<Pessoa, Integer> max = map.entrySet().stream()
.max(Map.Entry.comparingByValue(Integer::compareTo)).get();

System.out.println(max.getKey() + " tem " + max.getValue() + " anos");

Referências

1. [http://blog.caelum.com.br/o-minimo-que-voce-deve-saber-de-java-8/]
2. [https://zeroturnaround.com/rebellabs/java-8-explained-applying-lambdas-to-java-collections/]
3. [https://www.infoq.com/br/news/2013/10/tudo-sobre-java-8]

Categorias:Programação

Como Utilizar o Detector de Tipos de Arquivo do Apache Tika

O Tika é uma biblioteca da Apache que detecta e extrai metadados de diferentes tipos de arquivos. Já utilizei o Tika em dois projetos e tive pouco trabalho para configurar do jeito que eu precisava. No último projeto, eu precisava detectar se a extensão de um arquivo era xls ou xlsx.

Já vi muito código que apenas extraia a extensão à partir do nome do arquivo. Esses códigos vão desde simples parsers à partir do separador de extensão de arquivos até a utilização do FilenameUtils do Commons IO, que faz a mesma coisa. Porém, e se o arquivo não tiver extensão? E se alguém simplesmente alterar a extensão original do arquivo? Dependendo da sua regra de negócio, você poderia simplesmente invalidar arquivos sem extensão ou com extensão inválida, mas o Tika extrai as extensões do metadado, ou melhor, da assinatura do tipo do arquivo que está no próprio arquivo. Dessa forma, ele poderia até ser utilizado para análise forense computacional.

Nesse artigo, vamos demonstrar como utilizar o Apache Tika para determinar se um arquivo é uma planilha do tipo xls ou xlsx, que é uma extensão típica do MS-Office. Comecemos pelas dependências:

<dependency>
   <groupId>org.apache.tika</groupId>
   <artifactId>tika-core</artifactId>
   <version>1.14</version>
</dependency>
<dependency>
   <groupId>org.apache.tika</groupId>
   <artifactId>tika-parsers</artifactId>
   <version>1.4</version>
</dependency>
<dependency>
   <groupId>xerces</groupId>
   <artifactId>xercesImpl</artifactId>
   <version>2.11.0</version>
</dependency>

É importante observar que você deve utilizar a versão mais recente do xerces, pois algumas versões antigas estão levantando exceção ao tentar fazer parse de algum xml. Poderíamos utilizar diretamente os detectores do Tika, mas vamos fazer algo mais genérico. Vamos implementar um DirectoryStream.Filter[T], que é uma interface do java que permite decidir se um determinado arquivo deve ser aceito. O interessante é que nossa classe pode ser utilizada sozinha para testar o tipo de um arquivo ou pode ser combinada com filtros de diretório.

public class XLSFilter implements DirectoryStream.Filter<Path> {
   private Detector detector;
   private static final List<String> EXTENSIONS = Arrays.asList(".xls", ".xlsx");

   public XLSFilter() {
         List<Detector> detectors = new ArrayList<Detector>();
         /*
          * Detector especializado em analisar metadados do MS-Office.
          */
          detectors.add(new POIFSContainerDetector());
         /*
          * Detectores padrao.
          */
          detectors.add(MimeTypes.getDefaultMimeTypes());
         /*
          * Detector composto pelos detectores acima.
          */
          detector = new CompositeDetector(detectors);
    }

    @Override
    public boolean accept(Path path) throws IOException {
       BasicFileAttributes attrs = Files.readAttributes(path, 
          BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS);
       if (!attrs.isRegularFile()) {
          return false;
       }
       String extension = "";
       try {
          extension = detectarExtensao(path);
       } catch (MimeTypeException e) {
          e.printStackTrace();
       }
       return EXTENSIONS.contains(extension.toLowerCase());
   }

   private String detectarExtensao(Path path) 
         throws IOException, MimeTypeException {
      MediaType mediaType = detectarMediaType(path);
      TikaConfig config = TikaConfig.getDefaultConfig();
      MimeType mimeType = config.getMimeRepository().forName(mediaType.toString());
      return mimeType == null ? "" : mimeType.getExtension();
   }

   private MediaType detectarMediaType(Path path) 
          throws IOException, MimeTypeException {
       Metadata metadata = new Metadata();
       TikaInputStream inputStream = TikaInputStream.get(path);
       return detector.detect(inputStream, metadata);
   }
}

Agora, vamos utilizar nosso detector para filtrar arquivos em um diretório:

   Path folder = Paths.get(PATH_DIR).resolve(SUB_DIR);
   DirectoryStream.Filter<Path> filter = XLSFilter();
   DirectoryStream<Path> stream = Files.newDirectoryStream(folder, filter);
   for (Path path : stream) {
      System.ou.println(path.toFile().getName());
   }

Referências

1. [https://tika.apache.org/]
2. [http://www.programcreek.com/java-api-examples/index.php?api=org.apache.tika.mime.MediaType]

Categorias:Programação

Como Integrar o Ehcache ao Spring

O Princípio de Pareto (regra 80-20), quando aplicado à análise da frequência com a qual nossa aplicação faz uso dos recursos, nos ajuda a estabelecer um critério para selecionar que eventos ou que entidades serão cacheadas. Esse princípio serve apenas para chamar nossa atenção para o fato de que há poucos eventos onerando uma boa parte da memória e do processamento, mas você deve utilizar ferramentas para investigar o que está acontecendo com seu aplicativo, como o VisualVM ou o JProfiler.

Conhecendo o que está onerando a memória, você pode decidir cachear ou bufferizar. Cache e buffer são conceitos diferentes, mas muitas vezes nós os utilizamos como sinônimos. Um buffer é estrutura temporária que media o relacionamento entre uma entidade rápida e outra lenta: o buffer diminui o tempo que a entidade rápida deve esperar pela disponibilidade da lenta oferecendo acesso direto a blocos inteiros ao invés de pedaços, o que melhora a performance.

Enquanto o buffer é uma estrutura conhecida por pelo menos uma das entidade comunicantes, o cache é feito sem que as entidades tomem conhecimento. Permitindo que uma entidade possa ser acessada muitas vezes, há ganho de performance, pois os recursos (persistência, storage, serviços) não precisarão ser acessados desnecessariamente e a entidade não precisará ser recriada.

O Ehcache é um dos mais famosos frameworks de cache. Ele oferece flexibilidade para escolhermos a estratégia de cache da nossa aplicação, como onde ele será armazenado (memória, disco), qual a quantidade máxima de entradas e quanto tempo uma entidade permanecerá em cache. Nesse artigo, vou mostrar como integrar o Ehcache ao framework Spring. Comecemos as configurações adicionando as dependências do Ehcache e do Spring.

<dependency>
   <groupId>net.sf.ehcache</groupId>
   <artifactId>ehcache</artifactId>
   <version>2.9.0</version>
</dependency>
<dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-context</artifactId>
   <version>4.1.4.RELEASE</version>
</dependency>
<dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-context-support</artifactId>
   <version>4.1.4.RELEASE</version>
</dependency>

Adicione o arquivo ehcache.xml ao seu classpath. Esse arquivo contém as estratégias de cache que você está utilizando – você pode definir quantas quiser. No exemplo abaixo, criei uma estratégia chamada “cacheUsuarios” que armazena até 10000 itens em memória durante 300 segundos.

<?xml version="1.0"?>
<ehcache
   xsi:noNamespaceSchemaLocation="ehcache.xsd"
   updateCheck="true"
   monitoring="autodetect"
   dynamicConfig="true">
   <cache name="cacheUsuarios"
      maxEntriesLocalHeap="10000"
      timeToLiveSeconds="300" 
      eternal="false"
      memoryStoreEvictionPolicy="LRU">
      <pinning store="localMemory"/>
   </cache>
</ehcache>

Você deve dizer ao Spring via @Configuration para ele ativar as anotações de cache definidas por @EnableCaching.

import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.ehcache.EhCacheCacheManager;
import org.springframework.cache.ehcache.EhCacheManagerFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;

@Configuration
@EnableCaching
public class CacheConfig {
   @Bean
   public CacheManager cacheManager() {
      return new EhCacheCacheManager(ehCacheCacheManager().getObject());
   }
   @Bean
   public EhCacheManagerFactoryBean ehCacheCacheManager() {
      EhCacheManagerFactoryBean cmfb = new EhCacheManagerFactoryBean();
      cmfb.setConfigLocation(new ClassPathResource("ehcache.xml"));
      cmfb.setShared(true);
      return cmfb;
   }
}

Crie a classe Usuario. Instâncias dessa classe serão cacheadas:

public class Usuario{
   private Integer id;
   private String nome;
}

Por último, basta anotar o método que devolve a entidade que será cacheada.

@Service
public class UsuarioServiceImpl implements UsuarioService {
   @Autowired
   private UsuarioDao dao;

   @Override
   @Transactional(readOnly = true)
   @Cacheable(value = "cacheUsuarios", key = "#nome")
   public Usuario find(String nome) throws Exception {
      return dao.find(nome);
   }
}

Note que definimos que a entrada onde a entidade será armazenada no cache tem como chave o “nome” do usuário, que foi informado via parâmetro. Poderíamos definir uma chave mais complexa à partir das propriedades de uma outra entidade. Se for esse o caso, você poderia definir algo assim:

#entidade.propriedade1.propriedade3.propriedade3.propriedade4

Referências

1. [https://www.mkyong.com/spring/spring-caching-and-ehcache-example/]
2. [http://docs.spring.io/spring/docs/current/spring-framework-reference/html/cache.html]
3. [http://www.codingpedia.org/ama/spring-caching-with-ehcache/]
4. [http://www.baeldung.com/spring-cache-tutorial]
5. [http://stackoverflow.com/questions/26140702/spring-cacheable-how-to-configure-a-complex-key]

Categorias:Programação

Criando sua Própria Tag Library do Shiro

Se você precisa de mais granularidade na checagem de permissões em uma página web, como por exemplo determinar se um botão deve ou não ser exibido, o Shiro oferece uma API que te permite fazer sua implementação nos métodos do seu controller/managed bean – não faça scriplets! No exemplo do botão, você poderia condicionar sua exibição assim:

<html xmlns="http://www.w3.org/1999/xhtml"
   xmlns:p="http://primefaces.org/ui">
   <body>
      <p:commandLink action="#{appMB.executar()}" 
          rendered="#{appMB.podeExecutar()}">Executar</p:commandLink>
   </body>
</html>

A implementação do método podeExecutar() utiliza a API do Shiro para verificar se o usuário tem determinada permissão:

public boolean podeExecutar() throws IOException {
   Subject subject = SecurityUtils.getSubject();
   return subject.isPermitted("PERMISSAO_EXECUCAO");
}

O problema é que você tende a reescrever esse mesmo código ou um muito parecido em cada managed bean que precisar de autorização. Felizmente, o Shiro possui uma tag library útil para quem trabalha com arquivos JSP, mas se você trabalha com JSF, não há nada pronto para utilizar. Porém, é possível criar sua própria biblioteca reutilizável à partir daquele código de checagem que você inseriu no managed bean. Vamos definir a assinatura de uma tag chamada “hasPermissao” que receba como parâmetro uma permissao:

<?xml version="1.0" encoding="UTF-8"?>
<facelet-taglib 
   version="2.2"
   xmlns="http://xmlns.jcp.org/xml/ns/javaee"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee 
   http://xmlns.jcp.org/xml/ns/javaee/web-facelettaglibrary_2_2.xsd">
   <namespace>http://app.com.br/security</namespace>
   <tag>
      <description><![CDATA[Verifica a permissao do usuario]]></description>
      <tag-name>hasPermissao</tag-name>
      <component>
         <component-type>permitido</component-type>
      </component>
      <attribute>
         <description><![CDATA[Permissao a ser verificada.]]></description>
         <name>permissao</name>
         <required>true</required>
         <type>java.lang.String</type>
      </attribute>
   </tag>
</facelet-taglib>

Uma vez criada a biblioteca, ela deve ser cadastrada no web.xml:

<context-param>
   <param-name>javax.faces.FACELETS_LIBRARIES</param-name>
   <param-value>/WEB-INF/app.taglib.xml</param-value>
</context-param>

Em seguida, vamos criar uma classe que extende UIComponentBase e é anotada como @FacesComponent. Essa implementação “esconde” todos os componentes visuais delimitados pela tag caso o usuário não esteja autorizado:

@FacesComponent(createTag = true, 
   namespace = "http://app.com.br/security", 
   tagName = "hasPermissao", 
   value = "hasPermissao")
public class PermissaoTag extends UIComponentBase {

   @Override
   public String getFamily() {
      return "app";
   }

   @Override
   public void encodeBegin(FacesContext context)
      throws IOException {
      String permissao = (String)getAttributes().get("permissao");
      Subject subject = SecurityUtils.getSubject();
      if (!subject.isPermitted(permissao)) {
         getChildren().stream().forEach(
            s -> s.setRendered(false));
      }
   }
}

Por último, importe a tag em seu arquivo xhtml e utilize no trecho de código sujeito à autorização:

<html xmlns="http://www.w3.org/1999/xhtml"
   xmlns:p="http://primefaces.org/ui"
   xmlns:app="http://app.com.br/security">
   <body>
     <app:hasPermissao permissao="PERMISSAO_EXECUCAO">
       <p:commandLink action="#{appMB.executar()}">Executar</p:commandLink>
     </app:hasPermissao>
   </body>
</html>

Referências

1. [https://www.mkyong.com/jsf2/custom-tags-in-jsf-2-0/]
2. [https://shiro.apache.org/web.html#Web-taglibrary]
3. [https://shiro.apache.org/java-authorization-guide.html#programmatic-authorization]