Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 39 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ It works seamlessly across AEM on-premise, AMS, and AEMaaCS environments.
- [Content scripts](#content-scripts)
- [Minimal example](#minimal-example)
- [Inputs example](#inputs-example)
- [Outputs example](#outputs-example)
- [ACL example](#acl-example)
- [Repo example](#repo-example)
- [History](#history)
Expand Down Expand Up @@ -251,10 +252,47 @@ When the script is executed, the inputs are passed to the `doRun()` method.

There are many built-in input types to use handling different types of data like string, boolean, number, date, file, etc. Just check `inputs` [service](https://github.com/wttech/acm/blob/main/core/src/main/java/dev/vml/es/acm/core/code/Inputs.java) for more details.

<img src="docs/screenshot-content-script-arguments.png" width="720" alt="ACM Console">
<img src="docs/screenshot-content-script-inputs.png" width="720" alt="ACM Content Script Inputs">

Be inspired by reviewing examples like [page thumbnail script](https://github.com/wttech/acm/blob/main/ui.content.example/src/main/content/jcr_root/conf/acm/settings/script/manual/example/ACME-202_page-thumbnail.groovy) which allows user to upload a thumbnail image and set it as a page thumbnail with only a few clicks and a few lines of code.

#### Outputs example

Scripts can generate output files that can be downloaded after execution.

The following example of the content script demonstrates how to generate a CSV report as an output file using the `outputs` [service](https://github.com/wttech/acm/blob/main/core/src/main/java/dev/vml/es/acm/core/code/Outputs.java).

There is no limitation on the number of output files that can be generated by a script. Each output file can have its own label, description, and download name. All outputs are persisted in the history, allowing you to review and download them later.

```groovy
boolean canRun() {
return conditions.always()
}

void doRun() {
log.info "Users report generation started"

def report = outputs.make("report") {
label = "Report"
description = "Users report generated as CSV file"
downloadName = "report.csv"
}

def users = [
[name: "John", surname: "Doe", birth: "1991"],
[name: "Jane", surname: "Doe", birth: "1995"],
[name: "Jack", surname: "Doe", birth: "2000"]
]
for (def user : users) {
report.out.println("${user.name},${user.surname},${user.birth}")
}

log.info "Users report generation ended successfully"
}
```

<img src="docs/screenshot-content-script-outputs.png" width="720" alt="ACM Content Script Outputs">

#### ACL example

The following example of the automatic script demonstrates how to create a user and a group, assign permissions, and add members to the group using the [ACL service](https://github.com/wttech/acm/blob/main/core/src/main/java/dev/vml/es/acm/core/acl/Acl.java) (`acl`).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ private synchronized void maybeUpdateVariablesCache(ResourceResolver resolver) {
resolver.getUserID(),
ExecutionMode.PARSE,
Code.consoleMinimal(),
new InputValues(),
resolver)) {
context.getCodeContext().prepareRun(context);
variablesCache = context.getCodeContext().getBindingVariables();
Expand Down
38 changes: 9 additions & 29 deletions core/src/main/java/dev/vml/es/acm/core/code/Code.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package dev.vml.es.acm.core.code;

import dev.vml.es.acm.core.AcmException;
import dev.vml.es.acm.core.util.JsonUtils;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.lang3.builder.ToStringBuilder;
Expand All @@ -18,40 +16,27 @@ public class Code implements Executable {

private String content;

private InputValues inputs;

public Code() {
// for deserialization
}

public Code(String id, String content, InputValues inputs) {
public Code(String id, String content) {
this.id = id;
this.content = content;
this.inputs = inputs;
}

public static Map<String, Object> toJobProps(Executable executable) throws AcmException {
try {
Map<String, Object> result = new HashMap<>();
result.put(ExecutionJob.EXECUTABLE_ID_PROP, executable.getId());
result.put(ExecutionJob.EXECUTABLE_CONTENT_PROP, executable.getContent());
result.put(ExecutionJob.EXECUTABLE_INPUTS_PROP, JsonUtils.writeToString(executable.getInputs()));
return result;
} catch (IOException e) {
throw new AcmException("Cannot serialize code to JSON!", e);
}
Map<String, Object> result = new HashMap<>();
result.put(ExecutionJob.EXECUTABLE_ID_PROP, executable.getId());
result.put(ExecutionJob.EXECUTABLE_CONTENT_PROP, executable.getContent());
return result;
}

public static Code fromJob(Job job) {
try {
String id = job.getProperty(ExecutionJob.EXECUTABLE_ID_PROP, String.class);
String content = job.getProperty(ExecutionJob.EXECUTABLE_CONTENT_PROP, String.class);
InputValues inputs = JsonUtils.readFromString(
job.getProperty(ExecutionJob.EXECUTABLE_INPUTS_PROP, String.class), InputValues.class);
return new Code(id, content, inputs);
} catch (IOException e) {
throw new AcmException("Cannot deserialize code from JSON!", e);
}

String id = job.getProperty(ExecutionJob.EXECUTABLE_ID_PROP, String.class);
String content = job.getProperty(ExecutionJob.EXECUTABLE_CONTENT_PROP, String.class);
return new Code(id, content);
}

public static Code consoleMinimal() {
Expand All @@ -76,11 +61,6 @@ public String getContent() {
return content;
}

@Override
public InputValues getInputs() {
return inputs;
}

public String toString() {
return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
.append("id", id)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package dev.vml.es.acm.core.code;

import com.fasterxml.jackson.annotation.JsonIgnore;
import dev.vml.es.acm.core.AcmException;
import dev.vml.es.acm.core.util.ExceptionUtils;
import java.io.InputStream;
Expand All @@ -10,15 +11,10 @@
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;

public class ImmediateExecution implements Execution {
public class ContextualExecution implements Execution {

private final CodeOutput codeOutput;

private final Executable executable;

private final String id;

private final String userId;
@JsonIgnore
private final transient ExecutionContext context;

private final ExecutionStatus status;

Expand All @@ -28,37 +24,28 @@ public class ImmediateExecution implements Execution {

private final String error;

private final String instance;

public ImmediateExecution(
CodeOutput codeOutput,
Executable executable,
String id,
String userId,
ExecutionStatus status,
Date startDate,
Date endDate,
String error,
String instance) {
this.codeOutput = codeOutput;
this.executable = executable;
this.id = id;
this.userId = userId;
public ContextualExecution(
ExecutionContext context, ExecutionStatus status, Date startDate, Date endDate, String error) {
this.context = context;
this.status = status;
this.startDate = startDate;
this.endDate = endDate;
this.error = error;
this.instance = instance;
}

@JsonIgnore
public ExecutionContext getContext() {
return context;
}

@Override
public String getId() {
return id;
return context.getId();
}

@Override
public String getUserId() {
return userId;
return context.getUserId();
}

@Override
Expand Down Expand Up @@ -91,8 +78,8 @@ public String getError() {

@Override
public String getOutput() {
codeOutput.flush();
try (InputStream stream = codeOutput.read()) {
context.getOutput().flush();
try (InputStream stream = context.getOutput().read()) {
return IOUtils.toString(stream, StandardCharsets.UTF_8);
} catch (Exception e) {
return null;
Expand All @@ -101,17 +88,17 @@ public String getOutput() {

@Override
public String getInstance() {
return instance;
return context.getCodeContext().getOsgiContext().readInstanceState();
}

public InputStream readOutput() throws AcmException {
codeOutput.flush();
return codeOutput.read();
context.getOutput().flush();
return context.getOutput().read();
}

@Override
public Executable getExecutable() {
return executable;
return context.getExecutable();
}

@Override
Expand Down Expand Up @@ -148,18 +135,19 @@ public Builder error(Throwable e) {
return this;
}

public ImmediateExecution end(ExecutionStatus status) {
public ContextualExecution end(ExecutionStatus status) {
Date endDate = new Date();
return new ImmediateExecution(
context.getOutput(),
context.getExecutable(),
context.getId(),
context.getCodeContext().getResourceResolver().getUserID(),
status,
startDate,
endDate,
error,
context.getCodeContext().getOsgiContext().readInstanceState());
return new ContextualExecution(context, status, startDate, endDate, error);
}
}

@Override
public InputValues getInputs() {
return new InputValues(context.getInputs().values());
}

@Override
public OutputValues getOutputs() {
return new OutputValues(context.getOutputs().getDefinitions().values());
}
}
2 changes: 0 additions & 2 deletions core/src/main/java/dev/vml/es/acm/core/code/Executable.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,4 @@ public interface Executable extends Serializable {
String getId();

String getContent() throws AcmException;

InputValues getInputs();
}
6 changes: 5 additions & 1 deletion core/src/main/java/dev/vml/es/acm/core/code/Execution.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,13 @@ public interface Execution extends Serializable {

long getDuration();

String getOutput();

String getError();

String getOutput();
InputValues getInputs();

OutputValues getOutputs();

String getInstance();

Expand Down
16 changes: 16 additions & 0 deletions core/src/main/java/dev/vml/es/acm/core/code/ExecutionContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ public class ExecutionContext implements AutoCloseable {

private final Inputs inputs;

private InputValues inputValues;

private final Outputs outputs;

private final Schedules schedules;

private final Conditions conditions;
Expand All @@ -41,18 +45,21 @@ public ExecutionContext(
ExecutionMode mode,
Executor executor,
Executable executable,
InputValues inputValues,
CodeContext codeContext) {
this.id = id;
this.userId = userId;
this.mode = mode;
this.executor = executor;
this.executable = executable;
this.inputValues = inputValues;
this.codeContext = codeContext;
this.output = determineOutput(mode, codeContext, id);
this.printStream = new CodePrintStream(output.write(), String.format("%s|%s", executable.getId(), id));
this.schedules = new Schedules();
this.conditions = new Conditions(this);
this.inputs = new Inputs();
this.outputs = new Outputs();

customizeBinding();
}
Expand Down Expand Up @@ -134,6 +141,14 @@ public Inputs getInputs() {
return inputs;
}

void useInputValues() {
inputs.setValues(inputValues);
}

public Outputs getOutputs() {
return outputs;
}

public Schedules getSchedules() {
return schedules;
}
Expand All @@ -148,6 +163,7 @@ private void customizeBinding() {
binding.setVariable("schedules", schedules);
binding.setVariable("arguments", inputs); // TODO deprecated
binding.setVariable("inputs", inputs);
binding.setVariable("outputs", outputs);
binding.setVariable("conditions", conditions);
binding.setVariable("out", getOut());
binding.setVariable("log", getLogger());
Expand Down
Loading