Obtenga más información acerca de las reglas de calidad del código personalizadas ejecutadas por Cloud Manager como parte de las pruebas de calidad del código, en función de las prácticas recomendadas de ingeniería de AEM.
Los ejemplos de código que se proporcionan aquí son solo ilustrativos. Consulte la documentación de conceptos de SonarQube para obtener información sobre los conceptos y las reglas de calidad.
Las reglas completas de SonarQube no están disponibles para su descarga debido a la información de Adobe registrada. Puede descargar la lista completa de reglas mediante este vínculo. Continúe leyendo este documento para obtener descripciones y ejemplos de las reglas.
La siguiente sección detalla las reglas de SonarQube ejecutadas por Cloud Manager.
Los métodos Thread.stop()
y Thread.interrupt()
pueden ocasionar problemas difíciles de reproducir y, a veces, vulnerabilidades de seguridad. Su utilización debe ser objeto de un estricto seguimiento y validación. En general, transmitir mensajes es una manera más segura de lograr objetivos similares.
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();
}
}
}
El uso de una cadena de formato de una fuente externa (como un parámetro de solicitud o contenido generado por el usuario) puede exponer una aplicación a ataques de denegación de servicio. Hay circunstancias en las que una cadena de formato puede estar controlada externamente, pero solo se permite desde fuentes de confianza.
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);
}
Al ejecutar solicitudes HTTP desde una aplicación de AEM, es fundamental asegurarse de que se han configurado los tiempos de espera adecuados para evitar un consumo innecesario de procesos secundarios. Lamentablemente, el comportamiento predeterminado de ambos clientes del HTTP predeterminados de Java™ (java.net.HttpUrlConnection
) y el cliente de componentes HTTP de Apache que se utiliza con frecuencia nunca es emplear el tiempo de espera, por lo que se debe establecer explícitamente. Como práctica recomendada, estos tiempos de espera no deben superar los 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();
}
Objetos ResourceResolver
obtenidos de recursos del sistema de consumo ResourceResolverFactory
. Aunque existen medidas para recuperar estos recursos cuando ResourceResolver
ya no está en uso, es más eficiente cerrar explícitamente cualquier objeto ResourceResolver
llamando al método close()
.
Una idea errónea frecuente es que los objetos ResourceResolver
creados con una sesión de JCR existente no deben cerrarse explícitamente o que, al hacerlo, han cerrado la sesión de JCR subyacente. No suele ser el caso. Independientemente de cómo se abra ResourceResolver
, debe cerrarse cuando ya no se utiliza. Dado que ResourceResolver
implementa la interfaz Closeable
, también es posible utilizar la sintaxis try-with-resources
en lugar de invocar explícitamente 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
}
}
Como se describe en la sección Documentación de Sling, se desaconsejan los servlets de enlace por rutas. Los servlets enlazados a rutas no pueden utilizar controles de acceso JCR estándar y, como resultado, requieren un rigor de seguridad adicional. En lugar de utilizar servlets enlazados a rutas, se recomienda crear nodos en el repositorio y registrar servlets por tipo de recurso.
@Component(property = {
"sling.servlet.paths=/apps/myco/endpoint"
})
public class DontDoThis extends SlingAllMethodsServlet {
// implementation
}
En general, una excepción debe registrarse exactamente una vez. El registro de excepciones varias veces puede causar confusión, ya que no está claro cuántas veces se produjo una excepción. El patrón más común que lleva a esto es registrar y arrojar una excepción 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);
}
}
Otro patrón común que se debe evitar es registrar un mensaje y luego iniciar inmediatamente una excepción. Esta práctica generalmente indica que el mensaje de excepción termina duplicado en los archivos de registro.
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");
}
En general, el nivel de registro INFO debe utilizarse para demarcar acciones importantes y, de forma predeterminada, AEM está configurado para registrar a nivel INFO o superior. Los métodos GET y HEAD solo deben ser operaciones de solo lectura y, por lo tanto, no constituyen acciones importantes. Es probable que el registro en el nivel INFO como respuesta a solicitudes de GET o HEAD cree un ruido de registro significativo, lo que dificulta la identificación de información útil en los archivos de registro. El registro al administrar solicitudes de GET o HEAD debe realizarse en los niveles WARN o ERROR cuando algo ha salido mal o en los niveles DEBUG o TRACE si sería útil disponer de información más detallada sobre la solución de problemas.
Esto no se aplica al registro de tipo access.log para cada solicitud.
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áctica recomendada, los mensajes de registro deben proporcionar información contextual sobre dónde se ha producido una excepción en la aplicación. Aunque el contexto también se puede determinar mediante el uso de trazos de pila, en general, el mensaje de registro será más fácil de leer y comprender. Como resultado, al registrar una excepción, es una mala práctica utilizar el mensaje de la excepción como mensaje de registro. El mensaje de excepción contiene lo que ha salido mal, mientras que el mensaje de registro debe utilizarse para indicar a un lector de registro qué estaba haciendo la aplicación cuando se produjo la excepción. El mensaje de excepción se sigue registrando. Al especificar su propio mensaje, los registros son más fáciles 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 sugiere el nombre, las excepciones de Java™ siempre deben usarse en circunstancias excepcionales. Como resultado, cuando se captura una excepción, es importante asegurarse de que los mensajes de registro se registren en el nivel adecuado: ADVERTENCIA o ERROR. Esto garantiza que esos mensajes aparezcan correctamente en los registros.
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);
}
}
El contexto es fundamental para comprender los mensajes de registro. El uso de Exception.printStackTrace()
hace que solo el trazo de pila se envíe al flujo de error estándar, lo que provoca que se pierda todo el contexto. Además, en una aplicación multiproceso como AEM si se imprimen varias excepciones utilizando este método en paralelo, sus trazos de pila pueden superponerse, lo que genera una confusión significativa. Las excepciones solo deben registrarse a través del marco de trabajo 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);
}
}
El registro en AEM siempre se debe realizar a través del marco de trabajo de registro, SLF4J. La salida directa a los flujos de error estándar o de salida estándar pierde la información estructural y contextual proporcionada por el marco de trabajo de registro y, a veces, puede causar problemas de rendimiento.
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);
}
}
En general, las rutas que comienzan por /libs
y /apps
no deben estar codificadas, ya que las rutas a las que se refieren generalmente se almacenan como rutas relativas a la ruta de búsqueda de Sling (que está configurada en /libs,/apps
de forma predeterminada). El uso de la ruta absoluta puede introducir defectos sutiles que solo aparecerían más adelante en el ciclo de vida del proyecto.
public boolean dontDoThis(Resource resource) {
return resource.isResourceType("/libs/foundation/components/text");
}
public void doThis(Resource resource) {
return resource.isResourceType("foundation/components/text");
}
El planificador de Sling no debe utilizarse para tareas que requieren una ejecución garantizada. Los trabajos programados de Sling garantizan la ejecución y son más adecuados para los entornos agrupados y no agrupados.
Consulte la documentación sobre eventos de Apache Sling y gestión de trabajos para obtener más información sobre cómo se gestionan los trabajos de Sling en entornos agrupados.
La superficie de la API de AEM está en constante revisión para identificar las API para las que se desaconseja el uso y que, por lo tanto, se consideran obsoletas.
A menudo, estas API están en desuso al utilizar la anotación @Deprecated de Java™ estándar y, como tal, identificada por squid:CallToDeprecatedMethod
.
Sin embargo, hay casos en los que una API está en desuso en el contexto de AEM, pero puede que no esté en desuso en otros contextos. Esta regla identifica esta segunda clase.
La siguiente sección detalla las comprobaciones OakPAL ejecutadas por Cloud Manager.
OakPAL es un marco de trabajo, que valida paquetes de contenido usando un repositorio Oak independiente. Fue desarrollado por un socio de AEM y ganador del premio de 2019 AEM Rockstar Norteamérica.
La API de AEM contiene interfaces y clases de Java™ que solo están pensadas para utilizarse mediante código personalizado, pero no para implementarse. Por ejemplo, la interfaz com.day.cq.wcm.api.Page
está diseñada para que la implemente únicamente AEM.
Cuando se agregan nuevos métodos a estas interfaces, esos métodos adicionales no afectan al código existente que utiliza estas interfaces y, como resultado, la adición de nuevos métodos a estas interfaces se considera compatible con versiones anteriores. Sin embargo, si el código personalizado implementa una de estas interfaces, dicho código personalizado ha introducido un riesgo de compatibilidad con versiones anteriores para el cliente.
Las interfaces y clases que solo se pretenden implementar mediante AEM se anotan con org.osgi.annotation.versioning.ProviderType
o, a veces, con una anotación existente similar aQute.bnd.annotation.ProviderType
. Esta regla identifica los casos en los que se implementa una interfaz de este tipo o en los que una clase se amplía mediante código personalizado.
import com.day.cq.wcm.api.Page;
public class DontDoThis implements Page {
// implementation here
}
Una práctica recomendada clásica es que el árbol de contenido /libs
en el repositorio de contenido de AEM debe ser considerado de solo lectura por los clientes. Modificar nodos y propiedades en /libs
crea un riesgo significativo para las actualizaciones principales y secundarias. Las modificaciones en /libs
solo deben realizarse mediante Adobe a través de canales oficiales.
Un problema común que ocurre en proyectos complejos es que el mismo componente OSGi se configura varias veces. Esto crea una ambigüedad sobre la configuración que se puede utilizar. Esta regla es “según el modo de ejecución”, ya que solo identificará problemas en los que el mismo componente se haya configurado varias veces en el mismo modo de ejecución o en una combinación de modos de ejecución.
+ 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 seguridad, las rutas que contienen /config/
y /install/
solo pueden leerlas los usuarios administrativos en AEM y solo deben utilizarse para la configuración OSGi y los paquetes OSGi. Colocar otros tipos de contenido en rutas que contienen estos segmentos resulta en un comportamiento de la aplicación que varía involuntariamente entre usuarios administrativos y no administrativos.
Un problema común es el uso de nodos llamados config
en los cuadros de diálogo de componentes o al especificar la configuración del editor de texto enriquecido para la edición en línea. Para resolver esto, se debe cambiar el nombre del nodo infractor por uno conforme. Para la configuración del editor de texto enriquecido, utilice la propiedad configPath
en el nodo cq:inplaceEditing
para especificar la nueva ubicación.
+ 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]
Similar a la regla Los paquetes no deben contener configuraciones OSGi duplicadas, este es un problema frecuente en proyectos complejos en los que varios paquetes de contenido independientes escriben la misma ruta de acceso de nodo. Aunque las dependencias de paquetes de contenido se pueden utilizar para garantizar un resultado coherente, es mejor evitar superposiciones por completo.
La configuración OSGi com.day.cq.wcm.core.impl.AuthoringUIModeServiceImpl
define el modo de creación predeterminado en AEM. Dado que la IU clásica ha quedado obsoleta desde AEM 6.4, ahora se generará un problema cuando el modo de creación predeterminado esté configurado en la IU clásica.
Los componentes de AEM que tienen un cuadro de diálogo de interfaz de usuario clásica siempre deben tener un cuadro de diálogo de IU táctil correspondiente, tanto para proporcionar una experiencia de creación óptima como para ser compatibles con el modelo de implementación de Cloud Service, donde la interfaz de usuario clásica no es compatible. Esta regla verifica los siguientes escenarios:
dialog
secundario) debe tener un cuadro de diálogo correspondiente de la interfaz de usuario táctil (es decir, un nodo cq:dialog
secundario).design_dialog
) debe tener un cuadro de diálogo de diseño de la interfaz de usuario táctil correspondiente (es decir, un nodo cq:design_dialog
secundario).La documentación de Herramientas de modernización AEM proporciona información y herramientas sobre cómo convertir componentes de la IU clásica a la IU táctil. Consulte la documentación de Herramientas de modernización AEM para obtener más información.
Para ser compatible con el modelo de implementación del Cloud Service, los paquetes de contenido individuales deben contener contenido para las áreas inmutables del repositorio (es decir, /apps
y /libs
) o el área mutable (es decir, todo lo que no está en /apps
o /libs
), pero no ambas. Por ejemplo, un paquete que incluye /apps/myco/components/text and /etc/clientlibs/myco
no es compatible con Cloud Service y provocará que se notifique un problema.
Consulte Documentación de la estructura del proyecto AEM para obtener más información.
La regla Los paquetes de cliente no deben crear ni modificar nodos en /libs siempre se aplica.
La compatibilidad con la replicación inversa no está disponible en las implementaciones de Cloud Service, como se describe en Notas de la versión: Eliminación de agentes de replicación.
Los clientes que utilicen la replicación inversa deben ponerse en contacto con Adobe para obtener soluciones alternativas.
Las bibliotecas de cliente de AEM pueden contener recursos estáticos como imágenes y fuentes. Como se describe en el Uso de la documentación de las bibliotecas del lado cliente, cuando se utilizan bibliotecas cliente proxy, estos recursos estáticos deben estar contenidos en una carpeta secundaria denominada resources
para que se haga referencia de forma efectiva en las instancias de publicación.
+ apps
+ projectA
+ clientlib
- allowProxy=true
+ images
+ myimage.jpg
+ apps
+ projectA
+ clientlib
- allowProxy=true
+ resources
+ myimage.jpg
Con el cambio a los microservicios de activos para el procesamiento de activos en AEM Cloud Service, varios procesos de flujo de trabajo que se utilizaban en versiones On-Premise y de AMS de AEM han dejado de ser compatibles o son innecesarios.
La herramienta de migración en el Repositorio de GitHub de AEM Assets as a Cloud Service se puede utilizar para actualizar modelos de flujo de trabajo durante la migración a AEM as a Cloud Service.
Si bien el uso de plantillas estáticas siempre ha sido común, históricamente, en Proyectos AEM, se recomienda encarecidamente el uso de plantillas editables, ya que proporcionan la mayor flexibilidad y admiten funciones adicionales que no están presentes en las plantillas estáticas. Encontrará más información en el documento Plantillas de página: editables.
La migración de plantillas estáticas a editables se puede automatizar en gran medida mediante las Herramientas de modernización de AEM.
Los componentes básicos heredados (es decir, los componentes en /libs/foundation
) llevan en desuso varias versiones de AEM a favor de los Componentes principales. Se desaconseja el uso de los componentes básicos heredados como base para los componentes personalizados, ya sea por superposición o herencia, y deben convertirse al componente principal correspondiente.
Esta conversión puede facilitarse mediante las Herramientas de modernización de AEM.
AEM Cloud Service aplica una estricta directiva de nomenclatura para los nombres de los modos de ejecución y un orden estricto para esos modos de ejecución. La lista de modos de ejecución admitidos se encuentra en la documentación Implementación en AEM as a Cloud Service y cualquier desviación de esto se identificará como un problema.
AEM Cloud Service requiere que las definiciones de índice de búsqueda personalizadas (es decir, nodos de tipo oak:QueryIndexDefinition
) sean nodos secundarios directos de /oak:index
. Los índices de otras ubicaciones deben moverse para que sean compatibles con AEM Cloud Service. Puede encontrar más información sobre los índices de búsqueda en la documentación de búsqueda de contenido e indexación.
AEM Cloud Service requiere que las definiciones de índice de búsqueda personalizadas (es decir, nodos de tipo oak:QueryIndexDefinition
) tengan la propiedad compatVersion
establecida en 2
. AEM Cloud Service no admite ningún otro valor. Puede encontrar más información sobre los índices de búsqueda en la documentación de búsqueda de contenido e indexación.
Pueden producirse problemas difíciles de solucionar cuando un nodo de definición de índice de búsqueda personalizada tiene nodos secundarios sin ordenar. Para evitarlos, se recomienda que todos los nodos descendientes de un nodo oak:QueryIndexDefinition
sean de tipo nt:unstructured
.
Un nodo de definición de índice de búsqueda personalizada definido correctamente debe contener un nodo secundario denominado indexRules
que, a su vez, debe tener al menos un secundario. Puede encontrar más información en la documentación de Oak.
AEM Cloud Service requiere que las definiciones de índice de búsqueda personalizadas (es decir, nodos de tipo oak:QueryIndexDefinition
) tengan un nombre que siga un patrón específico descrito en Búsqueda e indexación de contenido.
AEM Cloud Service requiere que las definiciones de índice de búsqueda personalizadas (es decir, nodos de tipo oak:QueryIndexDefinition
) tengan la propiedad type
con el valor establecido en lucene
. La indexación mediante tipos de índice heredados debe actualizarse antes de la migración a AEM Cloud Service. Consulte la Documentación de búsqueda de contenido e indexación para obtener más información.
AEM Cloud Service prohíbe que las definiciones de índice de búsqueda personalizadas (es decir, los nodos de tipo oak:QueryIndexDefinition
) contengan una propiedad denominada seed
. La indexación que utiliza esta propiedad debe actualizarse antes de la migración a AEM Cloud Service. Consulte la Documentación de búsqueda de contenido e indexación para obtener más información.
AEM Cloud Service prohíbe que las definiciones de índice de búsqueda personalizadas (es decir, los nodos de tipo oak:QueryIndexDefinition
) contengan una propiedad denominada reindex
. La indexación que utiliza esta propiedad debe actualizarse antes de la migración a AEM Cloud Service. Consulte la Documentación de búsqueda de contenido e indexación para obtener más información.
En la sección siguiente se enumeran las comprobaciones de la Herramienta de optimización de Dispatcher (DOT) ejecutadas por Cloud Manager. Siga los vínculos para cada comprobación de su definición y detalles de GitHub.