Esta página descreve as regras de qualidade do código personalizado executadas pelo Cloud Manager como parte de teste de qualidade do código. Eles são baseados nas práticas recomendadas da engenharia de AEM.
As amostras de código fornecidas aqui são apenas para fins ilustrativos. Ver o SonarQube Documentação de conceitos para saber mais sobre os conceitos e as regras de qualidade do SonarQube.
A seção a seguir detalha as regras do SonarQube executadas pelo Cloud Manager.
Os métodos Thread.stop()
e Thread.interrupt()
pode gerar problemas de difícil reprodução e, em alguns casos, vulnerabilidades de segurança. A utilização deve ser rigorosamente monitorizada e validada. Em geral, a transmissão de mensagens é uma forma mais segura de atingir objetivos semelhantes.
public class DontDoThis implements Runnable {
private Thread thread;
public void start() {
thread = new Thread(this);
thread.start();
}
public void stop() {
thread.stop(); // UNSAFE!
}
public void run() {
while (true) {
somethingWhichTakesAWhileToDo();
}
}
}
public class DoThis implements Runnable {
private Thread thread;
private boolean keepGoing = true;
public void start() {
thread = new Thread(this);
thread.start();
}
public void stop() {
keepGoing = false;
}
public void run() {
while (this.keepGoing) {
somethingWhichTakesAWhileToDo();
}
}
}
O uso de uma string de formato de uma fonte externa (como um parâmetro de solicitação ou conteúdo gerado pelo usuário) pode produzir o pode expor um aplicativo a ataques de negação de serviço. Há circunstâncias em que uma string de formato pode ser controlada externamente, mas só é permitida a partir de fontes confiáveis.
protected void doPost(SlingHttpServletRequest request, SlingHttpServletResponse response) {
String messageFormat = request.getParameter("messageFormat");
request.getResource().getValueMap().put("some property", String.format(messageFormat, "some text"));
response.sendStatus(HttpServletResponse.SC_OK);
}
Ao executar solicitações HTTP de dentro de um aplicativo AEM, é importante garantir que os tempos limite adequados sejam configurados para evitar o consumo desnecessário de encadeamento. Infelizmente, o comportamento padrão do cliente HTTP padrão do Java (java.net.HttpUrlConnection
) e o cliente Apache HTTP Components usado com frequência é o de nunca expirar o tempo limite, portanto, os tempos limite devem ser definidos explicitamente. Além disso, como prática recomendada, esses tempos limite não devem ser superiores a 60 segundos.
@Reference
private HttpClientBuilderFactory httpClientBuilderFactory;
public void dontDoThis() {
HttpClientBuilder builder = httpClientBuilderFactory.newBuilder();
HttpClient httpClient = builder.build();
// do something with the client
}
public void dontDoThisEither() {
URL url = new URL("http://www.google.com");
URLConnection urlConnection = url.openConnection();
BufferedReader in = new BufferedReader(new InputStreamReader(
urlConnection.getInputStream()));
String inputLine;
while ((inputLine = in.readLine()) != null) {
logger.info(inputLine);
}
in.close();
}
@Reference
private HttpClientBuilderFactory httpClientBuilderFactory;
public void doThis() {
HttpClientBuilder builder = httpClientBuilderFactory.newBuilder();
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(5000)
.setSocketTimeout(5000)
.build();
builder.setDefaultRequestConfig(requestConfig);
HttpClient httpClient = builder.build();
// do something with the client
}
public void orDoThis() {
URL url = new URL("http://www.google.com");
URLConnection urlConnection = url.openConnection();
urlConnection.setConnectTimeout(5000);
urlConnection.setReadTimeout(5000);
BufferedReader in = new BufferedReader(new InputStreamReader(
urlConnection.getInputStream()));
String inputLine;
while ((inputLine = in.readLine()) != null) {
logger.info(inputLine);
}
in.close();
}
ResourceResolver
objetos obtidos do ResourceResolverFactory
consumir recursos do sistema. Embora existam medidas em vigor para recuperar esses recursos quando uma ResourceResolver
não estiver mais em uso, é mais eficiente fechar explicitamente qualquer aberto ResourceResolver
chamando a close()
método .
Um equívoco relativamente comum é que ResourceResolver
os objetos criados usando uma sessão JCR existente não devem ser explicitamente fechados ou isso encerrará a sessão JCR subjacente. Não é esse o caso. Independentemente de como uma ResourceResolver
for aberto, deve ser fechado quando não for mais usado. Since ResourceResolver
implementa o Closeable
, também é possível usar a variável try-with-resources
sintaxe em vez de chamar explicitamente close()
.
public void dontDoThis(Session session) throws Exception {
ResourceResolver resolver = factory.getResourceResolver(Collections.singletonMap("user.jcr.session", (Object)session));
// do some stuff with the resolver
}
public void doThis(Session session) throws Exception {
ResourceResolver resolver = null;
try {
resolver = factory.getResourceResolver(Collections.singletonMap("user.jcr.session", (Object)session));
// do something with the resolver
} finally {
if (resolver != null) {
resolver.close();
}
}
}
public void orDoThis(Session session) throws Exception {
try (ResourceResolver resolver = factory.getResourceResolver(Collections.singletonMap("user.jcr.session", (Object) session))){
// do something with the resolver
}
}
Conforme descrito na Documentação do Sling, os servlets de vinculação por caminhos não são incentivados. Os servlets vinculados ao caminho não podem usar controles de acesso JCR padrão e, como resultado, exigem rigor de segurança adicional. Em vez de usar servlets vinculados a caminhos, é recomendável criar nós no repositório e registrar servlets por tipo de recurso.
@Component(property = {
"sling.servlet.paths=/apps/myco/endpoint"
})
public class DontDoThis extends SlingAllMethodsServlet {
// implementation
}
Em geral, uma exceção deve ser registrada exatamente uma vez. O registro de exceções várias vezes pode causar confusão, pois não está claro quantas vezes uma exceção ocorreu. O padrão mais comum que leva a isso é o registro e o lançamento de uma exceção capturada.
public void dontDoThis() throws Exception {
try {
someOperation();
} catch (Exception e) {
logger.error("something went wrong", e);
throw e;
}
}
public void doThis() {
try {
someOperation();
} catch (Exception e) {
logger.error("something went wrong", e);
}
}
public void orDoThis() throws MyCustomException {
try {
someOperation();
} catch (Exception e) {
throw new MyCustomException(e);
}
}
Outro padrão comum a ser evitado é registrar uma mensagem e imediatamente lançar uma exceção. Isso geralmente indica que a mensagem de exceção acabará duplicada nos arquivos de log.
public void dontDoThis() throws Exception {
logger.error("something went wrong");
throw new RuntimeException("something went wrong");
}
public void doThis() throws Exception {
throw new RuntimeException("something went wrong");
}
Em geral, o nível de log INFO deve ser usado para demarcar ações importantes e, por padrão, AEM é configurado para fazer logon no nível INFO ou superior. Os métodos GET e HEAD só devem ser operações somente leitura e, portanto, não constituem ações importantes. Fazer logon no nível da INFO em resposta a solicitações de GET ou HEAD provavelmente gera um ruído significativo de log, dificultando a identificação de informações úteis em arquivos de log. O registro ao manipular solicitações de GET ou HEAD deve estar nos níveis de AVISO ou ERRO quando algo deu errado ou nos níveis de DEBUG ou TRACE, se informações mais detalhadas de solução de problemas forem úteis.
Isso não se aplica a access.log
-digite o log para cada solicitação.
public void doGet() throws Exception {
logger.info("handling a request from the user");
}
public void doGet() throws Exception {
logger.debug("handling a request from the user.");
}
Como prática recomendada, as mensagens de log devem fornecer informações contextuais sobre onde ocorreu uma exceção no aplicativo. Embora o contexto também possa ser determinado por meio do uso de rastreamentos de pilha, em geral a mensagem de log será mais fácil de ler e entender. Como resultado, ao registrar uma exceção, é uma prática ruim usar a mensagem da exceção como a mensagem de log. A mensagem de exceção conterá o que deu errado, enquanto a mensagem de log deve ser usada para informar a um leitor de log o que o aplicativo estava fazendo quando a exceção aconteceu. A mensagem de exceção ainda será registrada. Ao especificar sua própria mensagem, os logs serão mais fáceis de entender.
public void dontDoThis() {
try {
someMethodThrowingAnException();
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
}
public void doThis() {
try {
someMethodThrowingAnException();
} catch (Exception e) {
logger.error("Unable to do something", e);
}
}
Como o nome sugere, as exceções de Java devem sempre ser usadas em circunstâncias excepcionais. Como resultado, quando uma exceção é capturada, é importante garantir que as mensagens de log sejam registradas no nível apropriado, seja AVISO ou ERRO. Isso garante que essas mensagens sejam exibidas corretamente nos logs.
public void dontDoThis() {
try {
someMethodThrowingAnException();
} catch (Exception e) {
logger.debug(e.getMessage(), e);
}
}
public void doThis() {
try {
someMethodThrowingAnException();
} catch (Exception e) {
logger.error("Unable to do something", e);
}
}
Como mencionado, o contexto é essencial ao entender as mensagens de log. Usando Exception.printStackTrace()
faz com que somente o rastreamento de pilha seja enviado para o fluxo de erro padrão, perdendo todo o contexto. Além disso, em um aplicativo de vários segmentos como AEM, se várias exceções forem impressas em paralelo usando esse método, seus rastreamentos de pilha poderão se sobrepor, o que gera uma confusão significativa. As exceções devem ser registradas somente por meio da estrutura de registro.
public void dontDoThis() {
try {
someMethodThrowingAnException();
} catch (Exception e) {
e.printStackTrace();
}
}
public void doThis() {
try {
someMethodThrowingAnException();
} catch (Exception e) {
logger.error("Unable to do something", e);
}
}
O AEM de logon deve ser sempre feito por meio da estrutura de log (SLF4J). A saída diretamente para a saída padrão ou fluxos de erro padrão perde as informações estruturais e contextuais fornecidas pela estrutura de registro e pode, em alguns casos, causar problemas de desempenho.
public void dontDoThis() {
try {
someMethodThrowingAnException();
} catch (Exception e) {
System.err.println("Unable to do something");
}
}
public void doThis() {
try {
someMethodThrowingAnException();
} catch (Exception e) {
logger.error("Unable to do something", e);
}
}
Em geral, caminhos que começam com /libs
e /apps
não deve ser codificado, pois os caminhos a que se referem são mais comumente armazenados como caminhos relativos ao caminho de pesquisa do Sling, que está definido como /libs,/apps
por padrão. O uso do caminho absoluto pode apresentar defeitos sutis que só apareceriam posteriormente no ciclo de vida do projeto.
public boolean dontDoThis(Resource resource) {
return resource.isResourceType("/libs/foundation/components/text");
}
public void doThis(Resource resource) {
return resource.isResourceType("foundation/components/text");
}
O Sling Scheduler não deve ser usado para tarefas que exigem uma execução garantida. Os trabalhos agendados do Sling garantem a execução e são mais adequados para ambientes em cluster e não em cluster.
Consulte Evento do Apache Sling e Manuseio de Trabalho para saber mais sobre como os Sling Jobs são tratados em ambientes em cluster.
A superfície da API de AEM está sob revisão constante para identificar APIs para as quais o uso é desencorajado e, portanto, considerado obsoleto.
Em muitos casos, essas APIs são descontinuadas usando o Java padrão @Deprecated
anotação e, como tal, como identificada por squid:CallToDeprecatedMethod
.
No entanto, há casos em que uma API é preterida no contexto de AEM, mas pode não ser preterida em outros contextos. Essa regra identifica essa segunda classe.
A seção a seguir detalha as verificações do OakPAL executadas pelo Cloud Manager.
OakPAL é uma estrutura, que valida pacotes de conteúdo usando um repositório Oak independente. Ele foi desenvolvido por um Parceiro AEM e vencedor do prêmio AEM Rockstar North America 2019.
A API do AEM contém interfaces e classes do Java que devem ser usadas, mas não implementadas, apenas pelo código personalizado. Por exemplo, a interface com.day.cq.wcm.api.Page
foi projetada para ser implementada somente pelo AEM.
Quando novos métodos são adicionados a essas interfaces, esses métodos adicionais não afetam o código existente que usa essas interfaces e, como resultado, a adição de novos métodos a essas interfaces é considerada compatível com versões anteriores. No entanto, se o código personalizado implementa uma dessas interfaces, ele apresenta um risco de compatibilidade com versões anteriores para o cliente.
Interfaces e classes, que só devem ser implementadas por AEM, são anotadas com org.osgi.annotation.versioning.ProviderType
ou, em alguns casos, uma anotação herdada semelhante aQute.bnd.annotation.ProviderType
. Esta regra identifica os casos em que essa interface é implementada ou em que uma classe é estendida por um código personalizado.
import com.day.cq.wcm.api.Page;
public class DontDoThis implements Page {
// implementation here
}
Vários índices prontos para uso do Oak AEM incluem uma configuração tika e as personalizações desses índices devem incluir uma configuração tika. Essa regra verifica as personalizações do damAssetLucene
, lucene
e graphqlConfig
indexa e levanta um problema se a variável tika
estiver ausente ou se o nó tika
nó está faltando um nó filho chamado config.xml
.
Consulte a documentação de indexação para obter mais informações sobre como personalizar definições de índice.
+ oak:index
+ damAssetLucene-1-custom
- async: [async]
- evaluatePathRestrictions: true
- includedPaths: /content/dam
- reindex: false
- tags: [visualSimilaritySearch]
- type: lucene
+ oak:index
+ damAssetLucene-1-custom-2
- async: [async]
- evaluatePathRestrictions: true
- includedPaths: /content/dam
- reindex: false
- tags: [visualSimilaritySearch]
- type: lucene
+ tika
+ config.xml
Índices de Oak do tipo lucene
deve ser sempre indexado de forma assíncrona. Se isso não for feito, pode ocorrer instabilidade do sistema. Mais informações sobre a estrutura dos índices de lucene podem ser encontradas no Documentação do Oak.
+ oak:index
+ damAssetLucene-1-custom
- evaluatePathRestrictions: true
- includedPaths: /content/dam
- reindex: false
- type: lucene
- reindex: false
- tags: [visualSimilaritySearch]
- type: lucene
+ tika
+ config.xml
+ oak:index
+ damAssetLucene-1-custom-2
- async: [async]
- evaluatePathRestrictions: true
- includedPaths: /content/dam
- reindex: false
- tags: [visualSimilaritySearch]
- type: lucene
+ tika
+ config.xml
Para que a pesquisa de ativos funcione corretamente no AEM Assets, as personalizações da variável damAssetLucene
O índice Oak deve seguir um conjunto de diretrizes específicas a esse índice. Essa regra verifica se a definição do índice deve ter uma propriedade de vários valores chamada tags
que contém o valor visualSimilaritySearch
.
+ oak:index
+ damAssetLucene-1-custom
- async: [async, nrt]
- evaluatePathRestrictions: true
- includedPaths: /content/dam
- reindex: false
- type: lucene
+ tika
+ config.xml
+ oak:index
+ damAssetLucene-1-custom-2
- async: [async, nrt]
- evaluatePathRestrictions: true
- includedPaths: /content/dam
- reindex: false
- tags: [visualSimilaritySearch]
- type: lucene
+ tika
+ config.xml
Há muito que a melhor prática é que /libs
a árvore de conteúdo no repositório de conteúdo AEM deve ser considerada somente leitura pelos clientes. Modificação de nós e propriedades em /libs
cria riscos significativos para atualizações importantes e secundárias. Modificações em /libs
só devem ser efetuadas pelo Adobe através de canais oficiais.
Um problema comum que ocorre em projetos complexos é onde o mesmo componente OSGi é configurado várias vezes. Isso cria uma ambiguidade sobre qual configuração será aplicável. Essa regra é "sensível ao modo de execução" na medida em que só identificará problemas em que o mesmo componente é configurado várias vezes no mesmo modo de execução ou combinação de modos de execução.
Essa regra produzirá problemas em que a mesma configuração, no mesmo caminho, é definida em vários pacotes, incluindo casos em que o mesmo pacote é duplicado na lista geral de pacotes incorporados.
Por exemplo, se a build produzir pacotes nomeados com.myco:com.myco.ui.apps
e com.myco:com.myco.all
em que com.myco:com.myco.all
incorporar com.myco:com.myco.ui.apps
, em seguida, todas as configurações em com.myco:com.myco.ui.apps
serão relatadas como duplicatas.
Em geral, esse é um caso de não seguir a variável Diretrizes de estrutura do pacote de conteúdo.. Neste exemplo específico, o pacote com.myco:com.myco.ui.apps
está faltando o <cloudManagerTarget>none</cloudManagerTarget>
propriedade.
+ apps
+ projectA
+ config
+ com.day.cq.commons.impl.ExternalizerImpl
+ projectB
+ config
+ com.day.cq.commons.impl.ExternalizerImpl
+ apps
+ shared-config
+ config
+ com.day.cq.commons.impl.ExternalizerImpl
Por motivos de segurança, caminhos que contêm /config/
e /install/
só podem ser lidos por usuários administrativos no AEM e devem ser usados apenas para configuração do OSGi e pacotes OSGi. Colocar outros tipos de conteúdo em caminhos que contêm esses segmentos resulta no comportamento do aplicativo, que varia de forma não intencional entre usuários administrativos e não administrativos.
Um problema comum é o uso de nós nomeados config
nas caixas de diálogo do componente ou ao especificar a configuração do editor de rich text para edição em linha. Para resolver isso, o nó incorreto deve ser renomeado para um nome compatível. Para a configuração do editor de rich text, use o configPath
na cq:inplaceEditing
para especificar o novo local.
+ cq:editConfig [cq:EditConfig]
+ cq:inplaceEditing [cq:InplaceEditConfig]
+ config [nt:unstructured]
+ rtePlugins [nt:unstructured]
+ cq:editConfig [cq:EditConfig]
+ cq:inplaceEditing [cq:InplaceEditConfig]
./configPath = inplaceEditingConfig (String)
+ inplaceEditingConfig [nt:unstructured]
+ rtePlugins [nt:unstructured]
Semelhante ao Os pacotes não devem conter a regra de configurações OSGi duplicadas, esse é um problema comum em projetos complexos, onde o mesmo caminho de nó é gravado por vários pacotes de conteúdo separados. Embora seja possível usar as dependências do pacote de conteúdo para garantir um resultado consistente, é melhor evitar sobreposições completamente.
A configuração do OSGi com.day.cq.wcm.core.impl.AuthoringUIModeServiceImpl
define o modo de criação padrão no AEM. Porque a interface do usuário clássica está obsoleta desde o AEM 6.4, um problema será gerado quando o modo de criação padrão estiver configurado para a interface do usuário clássica.
Os componentes AEM que têm uma caixa de diálogo da interface clássica devem sempre ter uma caixa de diálogo da interface do usuário de toque correspondente, para fornecer uma experiência de criação ideal e para serem compatíveis com o modelo de implantação do Cloud Service, onde a interface do usuário clássica não é compatível. Essa regra verifica os seguintes cenários:
dialog
nó filho) deve ter uma caixa de diálogo da interface do usuário de toque correspondente (ou seja, um cq:dialog
nó filho).design_dialog
nó ) deve ter uma caixa de diálogo de design da interface do usuário de toque correspondente (ou seja, um cq:design_dialog
nó filho).A documentação das Ferramentas de Modernização do AEM fornece documentação e ferramentas para como converter componentes da interface clássica para a interface do usuário de toque. Consulte a documentação das Ferramentas de Modernização do AEM para obter mais detalhes.
Para serem compatíveis com o modelo de implantação do Cloud Service, os pacotes de conteúdo individuais devem conter conteúdo para as áreas imutáveis do repositório (ou seja, /apps
e /libs
) ou a área mutável (isto é, tudo não está em /apps
ou /libs
), mas não ambos. Por exemplo, um pacote que inclui ambos /apps/myco/components/text and /etc/clientlibs/myco
O não é compatível com o Cloud Service e causará a criação de um relatório de problemas.
A regra Os pacotes de clientes não devem criar ou modificar nós em /libs sempre se aplica.
Consulte Estrutura do projeto AEM para obter mais detalhes.
O suporte para replicação reversa não está disponível em implantações de Cloud Service, conforme descrito como parte AEM do as a Cloud Service notas de versão.
Os clientes que usam replicação inversa devem entrar em contato com o Adobe para obter soluções alternativas.
AEM bibliotecas de clientes podem conter recursos estáticos, como imagens e fontes. Conforme descrito no documento Uso de pré-processadores, ao usar bibliotecas de cliente proxy, esses recursos estáticos devem estar contidos em uma pasta filho chamada resources
para ser efetivamente referenciado nas instâncias de publicação.
+ apps
+ projectA
+ clientlib
- allowProxy=true
+ images
+ myimage.jpg
+ apps
+ projectA
+ clientlib
- allowProxy=true
+ resources
+ myimage.jpg
Com a mudança para microsserviços de ativos para processamento de ativos em AEM as a Cloud Service, vários processos de fluxo de trabalho que foram usados nas versões local e AMS de AEM se tornaram incompatíveis ou desnecessários.
A ferramenta de migração no Repositório GitHub as a Cloud Service da AEM Assets O pode ser usado para atualizar modelos de fluxo de trabalho durante a migração para AEM as a Cloud Service.
Embora o uso de modelos estáticos tenha sido historicamente muito comum em projetos AEM, os modelos editáveis são altamente recomendados, pois fornecem mais flexibilidade e suporte a recursos adicionais não presentes em modelos estáticos. Mais informações podem ser encontradas no documento Modelos de página.
A migração de modelos estáticos para editáveis pode ser amplamente automatizada usando o AEM Ferramentas de Modernização.
Os componentes básicos herdados (ou seja, os componentes em /libs/foundation
) foram obsoleto para várias versões de AEM a favor dos componentes principais. O uso dos componentes de base como a base para os componentes personalizados (seja por sobreposição ou herança) não é recomendado e deve ser convertido para os Componentes principais correspondentes.
Essa conversão pode ser facilitada pela AEM Ferramentas de Modernização.
AEM as a Cloud Service impõe uma política de nomeação estrita para nomes de modo de execução e uma ordem estrita para esses modos de execução. A lista de modos de execução suportados pode ser encontrada no documento Implantação para AEM as a Cloud Service e qualquer desvio em relação a isso será identificado como um problema.
AEM as a Cloud Service exige que as definições de índice de pesquisa personalizadas (ou seja, nós do tipo oak:QueryIndexDefinition
) ser nós filhos diretos de /oak:index
. Os índices em outros locais devem ser movidos para serem compatíveis com AEM as a Cloud Service. Mais informações sobre índices de pesquisa podem ser encontradas no documento Pesquisa e indexação de conteúdo.
AEM as a Cloud Service exige que as definições de índice de pesquisa personalizadas (ou seja, nós do tipo oak:QueryIndexDefinition
) deve ter o compatVersion
propriedade definida como 2
. Nenhum outro valor é suportado por AEM as a Cloud Service. Mais informações sobre índices de pesquisa podem ser encontradas em Pesquisa e indexação de conteúdo.
Difícil de solucionar problemas pode ocorrer quando um nó de definição de índice de pesquisa personalizado tem nós filhos desordenados. Para evitar essa situação, é recomendável que todos os nós descendentes de um oak:QueryIndexDefinition
nó ser do tipo nt:unstructured
.
Um nó de definição de índice de pesquisa personalizado corretamente definido deve conter um nó filho chamado indexRules
que, por sua vez, deve ter pelo menos um filho. Mais informações podem ser encontradas no Documentação do Oak.
AEM as a Cloud Service requer definições de índice de pesquisa personalizadas (ou seja, nós do tipo oak:QueryIndexDefinition
) deve ser nomeado de acordo com um padrão específico descrito no documento Pesquisa e indexação de conteúdo.
AEM as a Cloud Service exige que as definições de índice de pesquisa personalizadas (ou seja, nós do tipo oak:QueryIndexDefinition
) têm um type
com o valor definido como lucene
. A indexação usando tipos de índice herdados deve ser atualizada antes da migração para AEM as a Cloud Service. Consulte o documento Pesquisa e indexação de conteúdo para obter mais informações.
AEM as a Cloud Service proíbe definições de índice de pesquisa personalizadas (ou seja, nós do tipo oak:QueryIndexDefinition
) de conter uma propriedade chamada seed
. A indexação usando essa propriedade deve ser atualizada antes da migração para AEM as a Cloud Service. Consulte o documento Pesquisa e indexação de conteúdo para obter mais informações.
AEM as a Cloud Service proíbe definições de índice de pesquisa personalizadas (ou seja, nós do tipo oak:QueryIndexDefinition
) de conter uma propriedade chamada reindex
. A indexação usando essa propriedade deve ser atualizada antes da migração para AEM as a Cloud Service. Consulte o documento Pesquisa e indexação de conteúdo para obter mais informações.