Skip to content

Commit 43310dc

Browse files
Merge pull request #265 from wttech/file-output-speedy
File output / FS
2 parents ef3e2d3 + c28f14d commit 43310dc

File tree

4 files changed

+140
-22
lines changed

4 files changed

+140
-22
lines changed

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,12 @@ private void saveFileOutput(FileOutput output, Resource entry) {
9191
"Output '%s' cannot be flushed before saving to the execution history!", output.getName()),
9292
e);
9393
}
94-
file.saveFile(output.getMimeType(), output.getInputStream());
94+
try (InputStream inputStream = output.getInputStream()) {
95+
file.saveFile(output.getMimeType(), inputStream);
96+
} catch (IOException e) {
97+
throw new AcmException(
98+
String.format("Output '%s' cannot be saved to the execution history!", output.getName()), e);
99+
}
95100
}
96101

97102
public InputStream readOutputByName(Execution execution, String name) {

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

Lines changed: 72 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,28 @@
11
package dev.vml.es.acm.core.code;
22

33
import com.fasterxml.jackson.annotation.JsonIgnore;
4-
import dev.vml.es.acm.core.gui.SpaSettings;
5-
import dev.vml.es.acm.core.osgi.OsgiContext;
6-
import dev.vml.es.acm.core.repo.RepoChunks;
4+
import dev.vml.es.acm.core.AcmException;
75
import java.io.Closeable;
6+
import java.io.File;
7+
import java.io.FileInputStream;
8+
import java.io.FileOutputStream;
89
import java.io.Flushable;
910
import java.io.IOException;
1011
import java.io.InputStream;
1112
import java.io.OutputStream;
1213
import java.io.PrintStream;
14+
import org.apache.commons.io.FileUtils;
1315
import org.apache.commons.lang3.StringUtils;
14-
import org.apache.sling.api.resource.ResourceResolverFactory;
1516

1617
public class FileOutput extends Output implements Flushable, Closeable {
1718

19+
private static final String TEMP_DIR = "acm/execution/file";
20+
21+
@JsonIgnore
22+
private transient File tempFile;
23+
1824
@JsonIgnore
19-
private transient RepoChunks repoChunks;
25+
private transient FileOutputStream fileOutputStream;
2026

2127
@JsonIgnore
2228
private transient PrintStream printStream;
@@ -59,28 +65,47 @@ public void setMimeType(String mimeType) {
5965
}
6066

6167
@JsonIgnore
62-
private RepoChunks getRepoChunks() {
63-
if (repoChunks == null) {
64-
OsgiContext osgi = executionContext.getCodeContext().getOsgiContext();
65-
SpaSettings spaSettings = osgi.getService(SpaSettings.class);
66-
ResourceResolverFactory resolverFactory = osgi.getService(ResourceResolverFactory.class);
67-
String chunkFolderPath = String.format(
68-
"%s/outputs/%s",
69-
ExecutionContext.varPath(executionContext.getId()), StringUtils.replace(getName(), "/", "-"));
70-
repoChunks =
71-
new RepoChunks(resolverFactory, chunkFolderPath, spaSettings.getExecutionFileOutputChunkSize());
68+
private File getTempFile() {
69+
if (tempFile == null) {
70+
File tmpDir = FileUtils.getTempDirectory();
71+
String contextPrefix = StringUtils.replace(executionContext.getId(), "/", "-");
72+
String sanitizedName = StringUtils.replace(getName(), "/", "-");
73+
String fileName = String.format("%s_%s.out", contextPrefix, sanitizedName);
74+
File result = new File(new File(tmpDir, TEMP_DIR), fileName);
75+
File parentDir = result.getParentFile();
76+
if (parentDir != null && !parentDir.exists()) {
77+
try {
78+
FileUtils.forceMkdir(parentDir);
79+
} catch (IOException e) {
80+
throw new AcmException(
81+
String.format("Cannot create temp directory for output '%s'!", getName()), e);
82+
}
83+
}
84+
this.tempFile = result;
7285
}
73-
return repoChunks;
86+
return tempFile;
7487
}
7588

7689
@JsonIgnore
7790
public OutputStream getOutputStream() {
78-
return getRepoChunks().getOutputStream();
91+
if (fileOutputStream == null) {
92+
try {
93+
fileOutputStream = new FileOutputStream(getTempFile());
94+
} catch (IOException e) {
95+
throw new AcmException(
96+
String.format("Cannot create output stream for file output '%s'!", getName()), e);
97+
}
98+
}
99+
return fileOutputStream;
79100
}
80101

81102
@JsonIgnore
82103
public InputStream getInputStream() {
83-
return getRepoChunks().getInputStream();
104+
try {
105+
return new FileInputStream(getTempFile());
106+
} catch (IOException e) {
107+
throw new AcmException(String.format("Cannot read file output '%s'!", getName()), e);
108+
}
84109
}
85110

86111
@JsonIgnore
@@ -96,11 +121,38 @@ public void flush() throws IOException {
96121
if (printStream != null) {
97122
printStream.flush();
98123
}
99-
getRepoChunks().flush();
124+
if (fileOutputStream != null) {
125+
fileOutputStream.flush();
126+
}
100127
}
101128

102129
@Override
103130
public void close() throws IOException {
104-
getRepoChunks().close();
131+
IOException exception = null;
132+
try {
133+
if (printStream != null) {
134+
printStream.close();
135+
}
136+
if (fileOutputStream != null) {
137+
fileOutputStream.close();
138+
}
139+
} catch (IOException e) {
140+
exception = e;
141+
} finally {
142+
try {
143+
if (tempFile != null && tempFile.exists()) {
144+
FileUtils.forceDelete(tempFile);
145+
}
146+
} catch (IOException e) {
147+
if (exception != null) {
148+
exception.addSuppressed(e);
149+
} else {
150+
exception = e;
151+
}
152+
}
153+
}
154+
if (exception != null) {
155+
throw exception;
156+
}
105157
}
106158
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import java.time.LocalDate
2+
import java.util.Random
3+
4+
boolean canRun() {
5+
return conditions.always()
6+
}
7+
8+
void describeRun() {
9+
inputs.integerNumber("count") { label = "Users to generate"; min = 1; value = 5000000 }
10+
inputs.text("firstNames") { label = "First names"; description = "One first name per line"; value = "John\nJane\nJack\nAlice\nBob"}
11+
inputs.text("lastNames") { label = "Last names"; description = "One last name per line"; value = "Doe\nSmith\nBrown\nJohnson\nWhite" }
12+
}
13+
14+
void doRun() {
15+
log.info "Users CSV report generation started"
16+
17+
int count = inputs.value("count")
18+
def firstNames = (inputs.value("firstNames")).readLines().findAll { it.trim() }
19+
def lastNames = (inputs.value("lastNames")).readLines().findAll { it.trim() }
20+
21+
def random = new Random()
22+
def now = LocalDate.now()
23+
def hundredYearsAgo = now.minusYears(100)
24+
25+
def report = outputs.file("report") {
26+
label = "Report"
27+
description = "Users report generated as CSV file"
28+
downloadName = "report.csv"
29+
}
30+
31+
// CSV header
32+
report.out.println("Name,Surname,Birth Date")
33+
34+
// Generate users
35+
(1..count).each { i ->
36+
context.checkAborted()
37+
38+
def name = firstNames[random.nextInt(firstNames.size())]
39+
def surname = lastNames[random.nextInt(lastNames.size())]
40+
def birthDate = randomDateBetween(hundredYearsAgo, now)
41+
42+
report.out.println("${name},${surname},${birthDate}")
43+
44+
if (i % 100 == 0) log.info("Generated ${i} users...")
45+
}
46+
47+
outputs.text("summary") {
48+
value = "Processed ${count} user(s)"
49+
}
50+
51+
log.info "Users CSV report generation ended successfully"
52+
}
53+
54+
LocalDate randomDateBetween(LocalDate start, LocalDate end) {
55+
long startDay = start.toEpochDay()
56+
long endDay = end.toEpochDay()
57+
long randomDay = startDay + new Random().nextInt((int)(endDay - startDay + 1))
58+
return LocalDate.ofEpochDay(randomDay)
59+
}

ui.content.example/src/main/content/jcr_root/conf/acm/settings/script/manual/example/ACME-203_outputs.groovy renamed to ui.content.example/src/main/content/jcr_root/conf/acm/settings/script/manual/example/ACME-203_output-xls.groovy

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ boolean canRun() {
1010
}
1111

1212
void describeRun() {
13-
inputs.integerNumber("count") { label = "Users to generate"; min = 1; value = 1000 }
13+
inputs.integerNumber("count") { label = "Users to generate"; min = 1; value = 100000 }
1414
inputs.text("firstNames") { label = "First names"; description = "One first name per line"; value = "John\nJane\nJack\nAlice\nBob"}
1515
inputs.text("lastNames") { label = "Last names"; description = "One last name per line"; value = "Doe\nSmith\nBrown\nJohnson\nWhite" }
1616
}
@@ -38,6 +38,8 @@ void doRun() {
3838
dateStyle.setDataFormat(helper.createDataFormat().getFormat("yyyy-mm-dd"))
3939

4040
(1..count).each { i ->
41+
context.checkAborted()
42+
4143
def name = firstNames[random.nextInt(firstNames.size())]
4244
def surname = lastNames[random.nextInt(lastNames.size())]
4345
def birthDate = randomDateBetween(hundredYearsAgo, now)

0 commit comments

Comments
 (0)