Macro Execution Rules
Explanation
A macro does not decide when it runs, or in what state: the macro transformation does, and it expects things of the macro in return. This page covers what a macro owes that engine; Rendering Macros covers what a macro is made of.
Restricted Mode
The transformation context can be set in restricted mode. It tells the macro that the rendering it is part of must not:
- modify the database or perform other privileged operations;
- perform expensive computations;
- perform any potentially dangerous operation, such as executing a script.
A macro doing any of these reads the flag, and then either does not execute at all or executes a reduced version of itself. Respecting it is the macro's own responsibility.
if (context.getTransformationContext().isRestricted())
{
// Do not execute, or leave out the part that is a concern.
}Macro Preparation
The transformation can also ask a macro to prepare a MacroBlock before executing it: pre-compute everything that is independent of any context and store it as an attribute of the block, so that each later execution reuses the result instead of computing it again. For a macro whose work is mostly context-independent, and whose block is executed many times, that is a large performance difference.
A macro opts in by overriding prepare, a default method that otherwise does nothing:
default void prepare(MacroBlock macroBlock) throws MacroPreparationExceptionWhat it stores must be clonable or immutable, because a prepared block can end up exposed in an unsafe environment.
Isolated Execution
A macro is free to modify any part of the XDOM while it executes, inserting content or further macro calls elsewhere in the document. Doing so is discouraged, because the WYSIWYG editor converts the rendered content back to wiki syntax: every such modification is persisted in the page the author then saves.
The engine pays for the possibility too. Since a macro may have inserted other macros that, by priority, have to run immediately after it, the transformation has to search the XDOM again after each execution to find the next macro. When it knows that cannot have happened, it keeps a queue of macros to execute instead, which is considerably faster.
A macro may therefore declare its execution isolated: a promise that it will not modify the XDOM. A macro that executes transformations over its content in a way that gives those transformations access to the XDOM must not make that promise, because the macros it runs could modify the XDOM themselves.
The promise is made by overriding isExecutionIsolated, which receives the same parameters and content as execute, so the answer can depend on how the macro was called:
/**
* @param parameters the parameters with which the macro would be executed
* @param content the content with which the macro would be executed
* @return {@code true}, if executing the macro with the parameters definitely won't modify the XDOM,
* {@code false}, otherwise.
*/
@Unstable
default boolean isExecutionIsolated(P parameters, String content)
{
return false;
}Replacing the Macro Itself
The one modification of the XDOM that is routine is a macro replacing its own macro call, which is how a deprecated macro hands over to the macro that supersedes it. It builds the replacement MacroBlock, swaps it into the parent of the old one, and returns no blocks of its own; the transformation recognises the situation and ignores the result of the execution.
@Override
public List<Block> execute(Object parameters, String content, MacroTransformationContext context)
throws MacroExecutionException
{
// Construct a new macro block to replace the old macro block.
MacroBlock oldMacroBlock = context.getCurrentMacroBlock();
Optional<String> oldValue = Optional.ofNullable(oldMacroBlock.getParameter("oldParameter"));
Map<String, String> newParameters = Map.of("param1", oldValue.orElse("default").replace("old", "new"));
MacroBlock replacement = new MacroBlock("replacement", newParameters, content, context.isInline());
// Replace the old macro block with the new macro block.
oldMacroBlock.getParent().replaceChild(replacement, oldMacroBlock);
return List.of();
}Declare correct content and parameter metadata on the macro being replaced even so, although none of it will ever be visible in the WYSIWYG editor: the macro refactoring relies on that metadata to find the macros whose content has to be adjusted when a page is moved.
FAQ
Does declaring isolated execution change anything else about my macro?
No. An isolated macro is executed in the regular order like any other, and it can still modify the context. The promise is only that it will not touch the XDOM.