JCR Cleaner is a Maven plugin that cleans Adobe Experience Manager (AEM) projects by removing runtime generated properties of Java Content Repository (JCR) nodes, such as cq:lastReplicated
, jcr:lastModified
and jcr:created
(by default, 13 types of properties are removed). The plugin aims to be a part of a standard toolkit of every AEM project in order to improve code readability, as well as reduce the amount of unnecessary and environment specific data stored in the codebase.
Code repositories for AEM projects contain a substantial number of files in FileVault Document View (DocView) format, named .content.xml
(DocView files). In a typical AEM project there can be several thousand of such files.
DocView files represent serialized JCR nodes and their properties. In many cases those files aren’t created externally, but are transferred into a code repository directly from JCR. However, JCR nodes, besides their normal properties, often have runtime generated properties, such as jcr:lastModifiedBy
or cq:lastReplicatedBy
. In such cases transfer of DocView files into a code repository directly from JCR results in putting to the code repository DocView files with both normal and runtime generated properties of JCR nodes.
Storing certain runtime generated properties of JCR nodes in a code repository in DocView files is a bad practice at least due to the following reasons:
-
Reduced Code Readability
The inclusion of runtime generated properties of JCR nodes into DocView files in a code repository can notably reduce the readability of a code repository. When a repository is saturated with machine-generated data, discerning the important code and configuration details becomes challenging. This obscurity complicates version control, code reviews, debugging, and overall understanding of the codebase. A secondary but still notable aspect of this issue is the impairment of static code analysis. Warnings or alerts generated by these tools due to runtime generated properties create unnecessary noise, making it harder to spot real code-related issues.
-
Environment Specific Data
Runtime generated properties of JCR nodes are often specific to the environment from which they were retrieved. Storing these properties in a code repository can lead to the inclusion of environment-specific data in the codebase. This can cause problems when moving the code to a different environment, such as staging or production.
The presence of unwanted runtime generated properties of JCR nodes in a code repository in DocView files and consequences of that problem are resolved by JCR Cleaner. It achieves this by removing those properties from the relevant files. By default, the properties that have the following names are removed:
-
cq:lastReplicated
-
cq:lastReplicatedBy
-
cq:lastReplicationAction
-
cq:lastModified
-
cq:lastModifiedBy
-
cq:lastPublished
-
cq:lastPublishedBy
-
jcr:lastModified
-
jcr:lastModifiedBy
-
jcr:created
-
jcr:createdBy
-
jcr:isCheckedOut
-
jcr:uuid
Before
Here is an example of a DocView file that has 20 runtime generated properties. Note that those properties don’t have a pattern where they are located (no rule one line - one property) and are scattered irregularly:
<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0"
xmlns:jcr="http://www.jcp.org/jcr/1.0"
xmlns:nt="http://www.jcp.org/jcr/nt/1.0"
xmlns:cq="http://www.day.com/jcr/cq/1.0"
jcr:primaryType="nt:unstructured"
jcr:title="Properties"
cq:lastReplicated="{Date}2023-03-15T11:00:00.000Z"
cq:lastReplicatedBy="admin"
cq:lastReplicationAction="Activate"
cq:lastModified="{Date}2023-03-13T09:30:00.000+01:00"
cq:lastModifiedBy="admin"
cq:lastPublished="{Date}2023-03-15T11:00:00.000Z"
cq:lastPublishedBy="admin"
jcr:lastModified="Thu Jun 17 2021 12:55:05 GMT+0000"
jcr:lastModifiedBy="admin"
jcr:created="Thu Jun 17 2021 19:55:05 GMT+0000"
jcr:createdBy="admin"
jcr:isCheckedOut="{Boolean}true"
jcr:uuid="f5b51baa-34a2-47b7-860b-391e885a0f9f"
sling:resourceType="cq/gui/components/authoring/dialog">
<content
jcr:primaryType="nt:unstructured" cq:lastReplicated="{Date}2023-03-15T11:00:00.000Z"
sling:resourceType="granite/ui/components/coral/foundation/fixedcolumns">
<items jcr:primaryType="nt:unstructured" cq:lastReplicated="{Date}2023-03-15T11:00:00.000Z">
<column cq:lastReplicated="{Date}2023-03-15T11:00:00.000Z" jcr:createdBy="admin"
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/container" jcr:created="Thu Jun 17 2021 19:55:05 GMT+0000">
<items jcr:primaryType="nt:unstructured">
<textUno
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
fieldLabel="Text Uno"
name="./textUno"
cq:lastModified="{Date}2023-03-13T09:30:00.000+01:00"/>
<textDuo
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
fieldLabel="Text Duo"
name="./textDuo" cq:lastModified="{Date}2023-03-13T09:30:00.000+01:00"/>
</items>
</column>
</items>
</content>
</jcr:root>
After
Following the cleanup by JCR Cleaner, all the runtime generated properties from the above file will be removed. Besides that, the content of the file wil be prettified in places where the removed properties were located so that the final result will look the following way:
<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0"
xmlns:jcr="http://www.jcp.org/jcr/1.0"
xmlns:nt="http://www.jcp.org/jcr/nt/1.0"
xmlns:cq="http://www.day.com/jcr/cq/1.0"
jcr:primaryType="nt:unstructured"
jcr:title="Properties"
sling:resourceType="cq/gui/components/authoring/dialog">
<content
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/fixedcolumns">
<items jcr:primaryType="nt:unstructured">
<column
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/container">
<items jcr:primaryType="nt:unstructured">
<textUno
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
fieldLabel="Text Uno"
name="./textUno"/>
<textDuo
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
fieldLabel="Text Duo"
name="./textDuo"/>
</items>
</column>
</items>
</content>
</jcr:root>
It was explained above that JCR Cleaner has a set of default names of runtime generated properties that are removed by default. However - as it will be elaborated in the next section - that default set can be customized. For instance, the file below contains 2 runtime generated properties: jcr:createdBy
and jcr:isCheckedOut
. Nevertheless, if JCR Cleaner is configured in such way that only jcr:createdBy
property should be removed, it will be the only deleted property in this file:
<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0"
xmlns:jcr="http://www.jcp.org/jcr/1.0"
xmlns:nt="http://www.jcp.org/jcr/nt/1.0"
jcr:primaryType="nt:unstructured"
jcr:title="Properties"
jcr:createdBy="admin" (1)
jcr:isCheckedOut="{Boolean}true" (2)
sling:resourceType="cq/gui/components/authoring/dialog"/>
<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0"
xmlns:jcr="http://www.jcp.org/jcr/1.0"
xmlns:nt="http://www.jcp.org/jcr/nt/1.0"
jcr:primaryType="nt:unstructured"
jcr:title="Properties"
jcr:isCheckedOut="{Boolean}true" (2)
sling:resourceType="cq/gui/components/authoring/dialog"/>
-
jcr:createdBy
- will be removed -
jcr:isCheckedOut
- will be kept
To use JCR Cleaner, add it to the plugins
section of a relevant pom.xml
file in an AEM project. Typically, it should be a parent pom.xml
file located in the repository root.
JCR Cleaner has a single predefined goal: clean-jcr
. This goal should be specified in the plugin declaration:
<plugins>
...
<plugin>
<groupId>eu.ciechanowiec</groupId>
<artifactId>jcrcleaner-maven-plugin</artifactId>
<version>1.1.0</version>
<executions>
<execution>
<goals>
<goal>clean-jcr</goal>
</goals>
</execution>
</executions>
</plugin>
...
<plugins>
JCR Cleaner includes a default configuration that will suffice for the vast majority of AEM projects. However, this configuration can be customized as follows:
<plugins>
...
<plugin>
<groupId>eu.ciechanowiec</groupId>
<artifactId>jcrcleaner-maven-plugin</artifactId>
<version>1.1.0</version>
<executions>
<execution>
<goals>
<goal>clean-jcr</goal>
</goals>
</execution>
</executions>
<configuration>
<isEnabled>false</isEnabled> (1)
<fileNameRegex>new-.+</fileNameRegex> (2)
<excludedAbsPathRegex>.*site-archive.*</excludedAbsPathRegex> (3)
<namesOfPropertiesToRemove> (4)
<namesOfPropertyToRemove>jcr:created</namesOfPropertyToRemove>
<namesOfPropertyToRemove>jcr:createdBy</namesOfPropertyToRemove>
</namesOfPropertiesToRemove>
</configuration>
</plugin>
...
<plugins>
-
isEnabled
-true
if the plugin should be enabled and perform cleaning when executed;false
otherwise. By default, this value istrue
, which means that by default the plugin is enabled. -
fileNameRegex
- Regex for file names. Only content of files whose names match the specified regex will be subject to cleaning. The default value is .content.xml, which is the default name for DocView files. -
excludedAbsPathRegex
- Regex for absolute paths to exclude. Content of all files whose absolute paths match the specified regex will be excluded from cleaning. By default, this value is not specified, which means that by default there are no files excluded from cleaning on the base of their absolute paths. -
namesOfPropertiesToRemove
- Names of properties of JCR nodes that should be removed from the matched files. Only properties that have the names specified in this collection will be removed. By default, those names are:-
cq:lastReplicated
-
cq:lastReplicatedBy
-
cq:lastReplicationAction
-
cq:lastModified
-
cq:lastModifiedBy
-
cq:lastPublished
-
cq:lastPublishedBy
-
jcr:lastModified
-
jcr:lastModifiedBy
-
jcr:created
-
jcr:createdBy
-
jcr:isCheckedOut
-
jcr:uuid
-
-
By default, execution of JCR Cleaner is bounded to the
generate-resources
phase of the default lifecycle of a Maven build. It means that by default the plugin will be executed every time whenmvn clean test
,mvn clean package
ormvn clean install
commands are run. -
During execution JCR Cleaner will analyze the relevant DocView files and perform targeted removing of runtime generated properties.
-
To execute JCR Cleaner separately from the build, run the following command:
mvn jcrcleaner:clean-jcr
You can add a
-X
flag to the command above to run it in a debug mode:mvn jcrcleaner:clean-jcr -X
-
Given an average AEM project with 2,000 DocView files, the execution of JCR Cleaner takes approximately 10 seconds. To minimize the build time, you can configure the plugin to be disabled by default (set <isEnabled> to false) and run it manually at regular intervals.
The program is subject to MIT No Attribution License
Copyright © 2023-2024 Herman Ciechanowiec
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so.
The Software is provided 'as is', without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose and noninfringement. In no event shall the authors or copyright holders be liable for any claim, damages or other liability, whether in an action of contract, tort or otherwise, arising from, out of or in connection with the Software or the use or other dealings in the Software.