Skip to content

Commit ae8b617

Browse files
Merge pull request #223 from wttech/outputs
Outputs
2 parents 659ffad + 06bc7f9 commit ae8b617

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+879
-165
lines changed

README.md

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ It works seamlessly across AEM on-premise, AMS, and AEMaaCS environments.
4545
- [Content scripts](#content-scripts)
4646
- [Minimal example](#minimal-example)
4747
- [Inputs example](#inputs-example)
48+
- [Outputs example](#outputs-example)
4849
- [ACL example](#acl-example)
4950
- [Repo example](#repo-example)
5051
- [History](#history)
@@ -251,10 +252,47 @@ When the script is executed, the inputs are passed to the `doRun()` method.
251252

252253
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.
253254

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

256257
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.
257258

259+
#### Outputs example
260+
261+
Scripts can generate output files that can be downloaded after execution.
262+
263+
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).
264+
265+
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.
266+
267+
```groovy
268+
boolean canRun() {
269+
return conditions.always()
270+
}
271+
272+
void doRun() {
273+
log.info "Users report generation started"
274+
275+
def report = outputs.make("report") {
276+
label = "Report"
277+
description = "Users report generated as CSV file"
278+
downloadName = "report.csv"
279+
}
280+
281+
def users = [
282+
[name: "John", surname: "Doe", birth: "1991"],
283+
[name: "Jane", surname: "Doe", birth: "1995"],
284+
[name: "Jack", surname: "Doe", birth: "2000"]
285+
]
286+
for (def user : users) {
287+
report.out.println("${user.name},${user.surname},${user.birth}")
288+
}
289+
290+
log.info "Users report generation ended successfully"
291+
}
292+
```
293+
294+
<img src="docs/screenshot-content-script-outputs.png" width="720" alt="ACM Content Script Outputs">
295+
258296
#### ACL example
259297

260298
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`).

core/src/main/java/dev/vml/es/acm/core/assist/Assistancer.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ private synchronized void maybeUpdateVariablesCache(ResourceResolver resolver) {
154154
resolver.getUserID(),
155155
ExecutionMode.PARSE,
156156
Code.consoleMinimal(),
157+
new InputValues(),
157158
resolver)) {
158159
context.getCodeContext().prepareRun(context);
159160
variablesCache = context.getCodeContext().getBindingVariables();

core/src/main/java/dev/vml/es/acm/core/code/Code.java

Lines changed: 9 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
package dev.vml.es.acm.core.code;
22

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

1917
private String content;
2018

21-
private InputValues inputs;
22-
2319
public Code() {
2420
// for deserialization
2521
}
2622

27-
public Code(String id, String content, InputValues inputs) {
23+
public Code(String id, String content) {
2824
this.id = id;
2925
this.content = content;
30-
this.inputs = inputs;
3126
}
3227

3328
public static Map<String, Object> toJobProps(Executable executable) throws AcmException {
34-
try {
35-
Map<String, Object> result = new HashMap<>();
36-
result.put(ExecutionJob.EXECUTABLE_ID_PROP, executable.getId());
37-
result.put(ExecutionJob.EXECUTABLE_CONTENT_PROP, executable.getContent());
38-
result.put(ExecutionJob.EXECUTABLE_INPUTS_PROP, JsonUtils.writeToString(executable.getInputs()));
39-
return result;
40-
} catch (IOException e) {
41-
throw new AcmException("Cannot serialize code to JSON!", e);
42-
}
29+
Map<String, Object> result = new HashMap<>();
30+
result.put(ExecutionJob.EXECUTABLE_ID_PROP, executable.getId());
31+
result.put(ExecutionJob.EXECUTABLE_CONTENT_PROP, executable.getContent());
32+
return result;
4333
}
4434

4535
public static Code fromJob(Job job) {
46-
try {
47-
String id = job.getProperty(ExecutionJob.EXECUTABLE_ID_PROP, String.class);
48-
String content = job.getProperty(ExecutionJob.EXECUTABLE_CONTENT_PROP, String.class);
49-
InputValues inputs = JsonUtils.readFromString(
50-
job.getProperty(ExecutionJob.EXECUTABLE_INPUTS_PROP, String.class), InputValues.class);
51-
return new Code(id, content, inputs);
52-
} catch (IOException e) {
53-
throw new AcmException("Cannot deserialize code from JSON!", e);
54-
}
36+
37+
String id = job.getProperty(ExecutionJob.EXECUTABLE_ID_PROP, String.class);
38+
String content = job.getProperty(ExecutionJob.EXECUTABLE_CONTENT_PROP, String.class);
39+
return new Code(id, content);
5540
}
5641

5742
public static Code consoleMinimal() {
@@ -76,11 +61,6 @@ public String getContent() {
7661
return content;
7762
}
7863

79-
@Override
80-
public InputValues getInputs() {
81-
return inputs;
82-
}
83-
8464
public String toString() {
8565
return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
8666
.append("id", id)

core/src/main/java/dev/vml/es/acm/core/code/ImmediateExecution.java renamed to core/src/main/java/dev/vml/es/acm/core/code/ContextualExecution.java

Lines changed: 32 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package dev.vml.es.acm.core.code;
22

3+
import com.fasterxml.jackson.annotation.JsonIgnore;
34
import dev.vml.es.acm.core.AcmException;
45
import dev.vml.es.acm.core.util.ExceptionUtils;
56
import java.io.InputStream;
@@ -10,15 +11,10 @@
1011
import org.apache.commons.lang3.builder.ToStringBuilder;
1112
import org.apache.commons.lang3.builder.ToStringStyle;
1213

13-
public class ImmediateExecution implements Execution {
14+
public class ContextualExecution implements Execution {
1415

15-
private final CodeOutput codeOutput;
16-
17-
private final Executable executable;
18-
19-
private final String id;
20-
21-
private final String userId;
16+
@JsonIgnore
17+
private final transient ExecutionContext context;
2218

2319
private final ExecutionStatus status;
2420

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

2925
private final String error;
3026

31-
private final String instance;
32-
33-
public ImmediateExecution(
34-
CodeOutput codeOutput,
35-
Executable executable,
36-
String id,
37-
String userId,
38-
ExecutionStatus status,
39-
Date startDate,
40-
Date endDate,
41-
String error,
42-
String instance) {
43-
this.codeOutput = codeOutput;
44-
this.executable = executable;
45-
this.id = id;
46-
this.userId = userId;
27+
public ContextualExecution(
28+
ExecutionContext context, ExecutionStatus status, Date startDate, Date endDate, String error) {
29+
this.context = context;
4730
this.status = status;
4831
this.startDate = startDate;
4932
this.endDate = endDate;
5033
this.error = error;
51-
this.instance = instance;
34+
}
35+
36+
@JsonIgnore
37+
public ExecutionContext getContext() {
38+
return context;
5239
}
5340

5441
@Override
5542
public String getId() {
56-
return id;
43+
return context.getId();
5744
}
5845

5946
@Override
6047
public String getUserId() {
61-
return userId;
48+
return context.getUserId();
6249
}
6350

6451
@Override
@@ -91,8 +78,8 @@ public String getError() {
9178

9279
@Override
9380
public String getOutput() {
94-
codeOutput.flush();
95-
try (InputStream stream = codeOutput.read()) {
81+
context.getOutput().flush();
82+
try (InputStream stream = context.getOutput().read()) {
9683
return IOUtils.toString(stream, StandardCharsets.UTF_8);
9784
} catch (Exception e) {
9885
return null;
@@ -101,17 +88,17 @@ public String getOutput() {
10188

10289
@Override
10390
public String getInstance() {
104-
return instance;
91+
return context.getCodeContext().getOsgiContext().readInstanceState();
10592
}
10693

10794
public InputStream readOutput() throws AcmException {
108-
codeOutput.flush();
109-
return codeOutput.read();
95+
context.getOutput().flush();
96+
return context.getOutput().read();
11097
}
11198

11299
@Override
113100
public Executable getExecutable() {
114-
return executable;
101+
return context.getExecutable();
115102
}
116103

117104
@Override
@@ -148,18 +135,19 @@ public Builder error(Throwable e) {
148135
return this;
149136
}
150137

151-
public ImmediateExecution end(ExecutionStatus status) {
138+
public ContextualExecution end(ExecutionStatus status) {
152139
Date endDate = new Date();
153-
return new ImmediateExecution(
154-
context.getOutput(),
155-
context.getExecutable(),
156-
context.getId(),
157-
context.getCodeContext().getResourceResolver().getUserID(),
158-
status,
159-
startDate,
160-
endDate,
161-
error,
162-
context.getCodeContext().getOsgiContext().readInstanceState());
140+
return new ContextualExecution(context, status, startDate, endDate, error);
163141
}
164142
}
143+
144+
@Override
145+
public InputValues getInputs() {
146+
return new InputValues(context.getInputs().values());
147+
}
148+
149+
@Override
150+
public OutputValues getOutputs() {
151+
return new OutputValues(context.getOutputs().getDefinitions().values());
152+
}
165153
}

core/src/main/java/dev/vml/es/acm/core/code/Executable.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,4 @@ public interface Executable extends Serializable {
1010
String getId();
1111

1212
String getContent() throws AcmException;
13-
14-
InputValues getInputs();
1513
}

core/src/main/java/dev/vml/es/acm/core/code/Execution.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,13 @@ public interface Execution extends Serializable {
2121

2222
long getDuration();
2323

24+
String getOutput();
25+
2426
String getError();
2527

26-
String getOutput();
28+
InputValues getInputs();
29+
30+
OutputValues getOutputs();
2731

2832
String getInstance();
2933

core/src/main/java/dev/vml/es/acm/core/code/ExecutionContext.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ public class ExecutionContext implements AutoCloseable {
3131

3232
private final Inputs inputs;
3333

34+
private InputValues inputValues;
35+
36+
private final Outputs outputs;
37+
3438
private final Schedules schedules;
3539

3640
private final Conditions conditions;
@@ -41,18 +45,21 @@ public ExecutionContext(
4145
ExecutionMode mode,
4246
Executor executor,
4347
Executable executable,
48+
InputValues inputValues,
4449
CodeContext codeContext) {
4550
this.id = id;
4651
this.userId = userId;
4752
this.mode = mode;
4853
this.executor = executor;
4954
this.executable = executable;
55+
this.inputValues = inputValues;
5056
this.codeContext = codeContext;
5157
this.output = determineOutput(mode, codeContext, id);
5258
this.printStream = new CodePrintStream(output.write(), String.format("%s|%s", executable.getId(), id));
5359
this.schedules = new Schedules();
5460
this.conditions = new Conditions(this);
5561
this.inputs = new Inputs();
62+
this.outputs = new Outputs();
5663

5764
customizeBinding();
5865
}
@@ -134,6 +141,14 @@ public Inputs getInputs() {
134141
return inputs;
135142
}
136143

144+
void useInputValues() {
145+
inputs.setValues(inputValues);
146+
}
147+
148+
public Outputs getOutputs() {
149+
return outputs;
150+
}
151+
137152
public Schedules getSchedules() {
138153
return schedules;
139154
}
@@ -148,6 +163,7 @@ private void customizeBinding() {
148163
binding.setVariable("schedules", schedules);
149164
binding.setVariable("arguments", inputs); // TODO deprecated
150165
binding.setVariable("inputs", inputs);
166+
binding.setVariable("outputs", outputs);
151167
binding.setVariable("conditions", conditions);
152168
binding.setVariable("out", getOut());
153169
binding.setVariable("log", getLogger());

0 commit comments

Comments
 (0)