Migrate AEM with Grace
JCR Hopper is a migration and reporting tool for AEM’s content repository. It has its own script format that is valid JSON and consists of a series of actions, each of which “hops” over nodes in the repository.
JCR Hopper also comes with a visual tool to create, preview and run the scripts.
Download the package (.zip file) from the “Releases” section in GitHub and install it using package manager.
Alternatively, if you don’t want the script builder GUI, you can also only deploy the bundle (.jar file).
Running a script requires a script (either as a JSON-encoded string or as an instance of com.swisscom.aem.tools.jcrhopper.config.Script
) and a runner (com.swisscom.aem.tools.jcrhopper.Runner
).
JCR hopper scripts comprise the following:
- A log level that determines which log messages are sent to the run handler.
- A list of hop configs that are run against a given node. Hops can also have their own descendant pipelines of hop configs.
- A list of parameters that the script supports. Parameters always have a default value, thus passing arguments to the script runner is always optional. Each parameter also has a name, a script builder input type hint and an evaluation type that determines how the default values/arguments are to be interpreted.
Typically, a runner is created by means of a builder using Runner#builder()
.
The builder offers various methods to configure the runner:
#addUtil(String, Object)
,#addUtils(Map<String, Object>)
: Register namespaces for utility classes/objects that are available in JEXL expressions and script blocks.#addVariable(String, Object)
,#addVariables(Map<String, Object>)
: Make some variables known to the script ahead of time.#addHop(Hop<?>)
,#addHops(Collection<Hop<?>>)
: Make hop types known to the runner. Note: scripts in JSON format can only be parsed once the hop types they are using are registered.#registerFile(String, Function<String, File>)
,#registerFiles(Map<String, Function<String, File>>)
: Register file creators that allow scripts to aggregate information into output files.#addDefaultUtils(boolean)
: if set totrue
, The script will know about thearrays
,stream
,class
, andcollections
utility namespaces.#runHandler(com.swisscom.aem.tools.jcrhopper.config.RunHandler)
: Set a listener that is informed about script output and events. The default run handler does nothing (but log messages are logged using Slf4j in any case).
Once the builder is configured, it can be turned into a runner with either #build(Script)
or #build(String)
(for JSON scripts).
A configured runner offers several overloads to run the script:
#run(javax.jcr.Node, boolean)
: Run the script on the given node. Save the session afterwards if the second argument istrue
.#run(javax.jcr.Session, boolean)
: Same as above but use the root node of the given session.#run(javax.jcr.Session, boolean, java.util.Map<String, String>)
,#run(javax.jcr.Node, boolean, java.util.Map<String, String>)
: Same as above but with arguments to be passed to the script. Only arguments matching declared parameters on the script will be taken into account.
The OSGi service com.swisscom.aem.tools.jcrhopper.osgi.RunnerService
is used to return pre-configured runner builders that already know about the default hops, utils and files.
To extend runner builders returned by the OSGi service, create a component that provides the com.swisscom.aem.tools.jcrhopper.osgi.RunnerBuilderExtension
service. Implement #configure
to configure runner builders that are created via RunnerService
.
The script builder is a visual tool designed to help you build, prototype and execute your scripts. It is available at /apps/jcr-hopper/script-builder.html
.
The com.swisscom.aem.tools.impl.http.HopRunnerServlet
servlet listens at /bin/servlets/jcr-hopper/run
(though that is configurable) for POST requests. Each request will trigger one script execution. The script, the commit flag and all script arguments are passed in the request.
The script will be executed with the JCR session that is associated with the privileges of the requesting HTTP user.
The servlet uses RunnerService
, so any registered RunnerBuilderExtension
s are available.
The response will be a new-line delimited list of JSON objects or strings.
To use cURL to execute a script, do the following:
# $DO_COMMIT is either "true" or "false"
# $AEM_INSTANCE is the base URL of your AEM instance
curl -X POST -F "_script=@/path/to/script.json" -F "_commit=$DO_COMMIT" "$AEM_INSTANCE/bin/servlets/jcr-hopper/run"
Additional arguments can also be passed with -F
.
Note: Usually, POST requests are subject to the CSRF filter, thus a :cq_csrf_token
would need to be added. However, the default configuration allowlists the cURL user agent to not require that.
All configurable fields are either JEXL expressions (when the result type is arbitrary) or JEXL string templates (when the result is String
).
Consult the JEXL syntax reference for further details.
Gets all child nodes of this node accessible through the current Session that match a given pattern. The pattern may be a full name, a partial name with one or more wildcard characters (*), or a disjunction of those (using the | character).
For more information, see Node#getNodes(String).
The nested hop pipeline will run on each matching child node.
Copy the current node recursively to a new destination.
The nested hop pipeline will run on the copied node.
Create a new node. If the given path is relative, it is based on the current node’s path (usually used to create a child node).
The nested hop pipeline will run on the created node.
Sets variables on the current scope, available in script runner hops and JEXL expressions of all subsequent hops and their nested pipelines.
This hop does not have a nested pipeline.
Loops over an arbitrary expression. Can be configured to assume the expression evaluates to an iterable of nodes, in which case that becomes the current node of the nested pipeline. Otherwise, the nested pipeline stays on the current node but the iteration value becomes usable.
Runs the nested pipeline only if the given JEXL expression is matched.
Moves the current node to a new location. Must not be used on the root node.
This hop does not have a nested pipeline.
Runs an XPath or SQL2 query against the JCR.
Runs the nested pipeline for each node found.
Changes the name of the given property on the current node.
This hop does not have a nested pipeline.
Changes the order of the current node.
This hop does not have a nested pipeline.
Resolves a node at a specific location.
Runs the nested pipeline on the resolved node.
Runs a script with arbitrary code. Currently, JEXL is supported as well as all languages known to the javax.script.ScriptEngineManager
.
This hop does not have a nested pipeline.
Sets a property on the current node.
This hop does not have a nested pipeline.
Catches exceptions thrown on the nested pipeline and ensures hop execution of the current pipeline continues.
Mutant Standard emoji are licensed CC BY-NC-SA 4.0 International.