Skip to content

Commit ce3120f

Browse files
Merge pull request #278 from wttech/repo-save-file-fix
Repo save file fix
2 parents 20be054 + c3aedd5 commit ce3120f

25 files changed

+369
-123
lines changed

core/src/main/java/dev/vml/es/acm/core/repo/RepoResource.java

Lines changed: 109 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@
33
import dev.vml.es.acm.core.util.*;
44
import java.io.*;
55
import java.nio.charset.StandardCharsets;
6-
import java.nio.file.Files;
76
import java.util.*;
87
import java.util.concurrent.Executors;
98
import java.util.function.Consumer;
109
import java.util.function.Function;
1110
import java.util.stream.Stream;
1211
import javax.jcr.Node;
12+
import javax.jcr.NodeIterator;
1313
import javax.jcr.Property;
1414
import javax.jcr.RepositoryException;
1515
import org.apache.commons.io.IOUtils;
@@ -35,6 +35,8 @@
3535
*/
3636
public class RepoResource {
3737

38+
public static final String FILE_MIME_TYPE_DEFAULT = "application/octet-stream";
39+
3840
private final Repo repo;
3941

4042
private final String path;
@@ -402,6 +404,50 @@ public RepoResource moveInPlace(RepoResource target, boolean replace) {
402404
return target;
403405
}
404406

407+
// AEM 6.5.0 has no 'resourceResolver.orderBefore' so adding own based on:
408+
// https://github.com/apache/sling-org-apache-sling-jcr-resource/commit/3b8f01d226124417bdfdba4ca2086114a73a7c5d
409+
public boolean orderBefore(String siblingName) {
410+
Node parentNode = parent().requireNode();
411+
String name = getName();
412+
try {
413+
long existingNodePosition = -1;
414+
long siblingNodePosition = -1;
415+
long index = 0;
416+
417+
NodeIterator nodeIterator = parentNode.getNodes();
418+
while (nodeIterator.hasNext()) {
419+
Node childNode = nodeIterator.nextNode();
420+
String childName = childNode.getName();
421+
422+
if (childName.equals(name)) {
423+
existingNodePosition = index;
424+
}
425+
if (siblingName != null && childName.equals(siblingName)) {
426+
siblingNodePosition = index;
427+
} else if (siblingName == null && childName.equals(name)) {
428+
if (existingNodePosition == nodeIterator.getSize() - 1) {
429+
return false;
430+
}
431+
}
432+
index++;
433+
}
434+
435+
if (siblingName != null
436+
&& existingNodePosition >= 0
437+
&& siblingNodePosition >= 0
438+
&& existingNodePosition == siblingNodePosition - 1) {
439+
return false;
440+
}
441+
442+
parentNode.orderBefore(name, siblingName);
443+
repo.commit(String.format("reordering resource '%s' before '%s'", path, siblingName));
444+
repo.getLogger().info("Reordered resource '{}' before '{}'", path, siblingName);
445+
return true;
446+
} catch (RepositoryException e) {
447+
throw new RepoException(String.format("Cannot reorder resource '%s' before '%s'", path, siblingName), e);
448+
}
449+
}
450+
405451
public RepoResource parent() {
406452
String parentPath = parentPath();
407453
if (parentPath == null) {
@@ -534,100 +580,92 @@ public RepoResourceState state() {
534580
return new RepoResourceState(path, true, resource.getValueMap());
535581
}
536582

537-
public RepoResource saveFile(String mimeType, Consumer<OutputStream> dataWriter) {
538-
saveFileInternal(mimeType, null, dataWriter);
539-
return this;
583+
public RepoResource saveFile(InputStream data) {
584+
return saveFile(FILE_MIME_TYPE_DEFAULT, data);
540585
}
541586

542-
public RepoResource saveFile(String mimeType, File file) {
543-
saveFile(mimeType, (OutputStream os) -> {
544-
try (InputStream is = Files.newInputStream(file.toPath())) {
545-
IOUtils.copy(is, os);
546-
} catch (IOException e) {
547-
throw new RepoException(String.format("Cannot write file '%s' to path '%s'!", file.getPath(), path), e);
548-
}
549-
});
587+
public RepoResource saveFile(String mimeType, InputStream data) {
588+
return saveFile(Collections.singletonMap(JcrConstants.JCR_MIMETYPE, mimeType), data);
589+
}
590+
591+
public RepoResource saveFile(Map<String, Object> properties, InputStream data) {
592+
saveFileInternal(properties, data, null);
550593
return this;
551594
}
552595

553-
public RepoResource saveFile(String mimeType, Object data) {
554-
saveFileInternal(mimeType, data, null);
596+
public RepoResource saveFile(Consumer<OutputStream> dataWriter) {
597+
return saveFile(FILE_MIME_TYPE_DEFAULT, dataWriter);
598+
}
599+
600+
public RepoResource saveFile(String mimeType, Consumer<OutputStream> dataWriter) {
601+
return saveFile(Collections.singletonMap(JcrConstants.JCR_MIMETYPE, mimeType), dataWriter);
602+
}
603+
604+
public RepoResource saveFile(Map<String, Object> properties, Consumer<OutputStream> dataWriter) {
605+
saveFileInternal(properties, null, dataWriter);
555606
return this;
556607
}
557608

558-
private void saveFileInternal(String mimeType, Object data, Consumer<OutputStream> dataWriter) {
609+
private void saveFileInternal(Map<String, Object> properties, InputStream data, Consumer<OutputStream> dataWriter) {
610+
if (properties == null) {
611+
properties = new HashMap<>();
612+
}
613+
if (!properties.containsKey(JcrConstants.JCR_MIMETYPE)) {
614+
Map<String, Object> propertiesMutable = new HashMap<>(properties);
615+
propertiesMutable.put(JcrConstants.JCR_MIMETYPE, FILE_MIME_TYPE_DEFAULT);
616+
properties = propertiesMutable;
617+
}
618+
559619
Resource mainResource = resolve();
560620
try {
561-
if (mainResource == null) {
562-
String parentPath = StringUtils.substringBeforeLast(path, "/");
563-
Resource parent = repo.getResourceResolver().getResource(parentPath);
564-
if (parent == null) {
565-
throw new RepoException(
566-
String.format("Cannot save file as parent path '%s' does not exist!", parentPath));
567-
}
568-
String name = StringUtils.substringAfterLast(path, "/");
569-
Map<String, Object> mainValues = new HashMap<>();
570-
mainValues.put(JcrConstants.JCR_PRIMARYTYPE, JcrConstants.NT_FILE);
571-
mainResource = repo.getResourceResolver().create(parent, name, mainValues);
621+
if (mainResource != null) {
622+
repo.getResourceResolver().delete(mainResource);
623+
}
572624

573-
Map<String, Object> contentValues = new HashMap<>();
574-
setFileContent(contentValues, mimeType, data, dataWriter);
575-
repo.getResourceResolver().create(mainResource, JcrConstants.JCR_CONTENT, contentValues);
625+
Resource parent = parent().resolve();
626+
if (parent == null) {
627+
throw new RepoException(
628+
String.format("Cannot save file as parent path '%s' does not exist!", parentPath()));
629+
}
630+
String name = getName();
631+
Map<String, Object> mainValues = new HashMap<>();
632+
mainValues.put(JcrConstants.JCR_PRIMARYTYPE, JcrConstants.NT_FILE);
633+
mainResource = repo.getResourceResolver().create(parent, name, mainValues);
576634

577-
repo.commit(String.format("creating file at path '%s'", path));
578-
repo.getLogger().info("Created file at path '{}'", path);
579-
} else {
580-
Resource contentResource = mainResource.getChild(JcrConstants.JCR_CONTENT);
581-
if (contentResource == null) {
582-
Map<String, Object> contentValues = new HashMap<>();
583-
setFileContent(contentValues, mimeType, data, dataWriter);
584-
repo.getResourceResolver().create(mainResource, JcrConstants.JCR_CONTENT, contentValues);
585-
} else {
586-
ModifiableValueMap contentValues =
587-
Objects.requireNonNull(contentResource.adaptTo(ModifiableValueMap.class));
588-
setFileContent(contentValues, mimeType, data, dataWriter);
589-
}
635+
Map<String, Object> contentValues = new HashMap<>();
636+
setFileContent(contentValues, properties, data, dataWriter);
637+
repo.getResourceResolver().create(mainResource, JcrConstants.JCR_CONTENT, contentValues);
590638

591-
repo.commit(String.format("updating file at path '%s'", path));
592-
repo.getLogger().info("Updated file at path '{}'", path);
593-
}
639+
repo.commit(String.format("saving file at path '%s'", path));
640+
repo.getLogger().info("Saved file at path '{}'", path);
594641
} catch (PersistenceException e) {
595642
throw new RepoException(String.format("Cannot save file at path '%s'!", path), e);
596643
}
597644
}
598645

599646
private void setFileContent(
600-
Map<String, Object> contentValues, String mimeType, Object data, Consumer<OutputStream> dataWriter) {
647+
Map<String, Object> contentValues,
648+
Map<String, Object> props,
649+
InputStream data,
650+
Consumer<OutputStream> dataWriter) {
651+
contentValues.put(JcrConstants.JCR_PRIMARYTYPE, JcrConstants.NT_RESOURCE);
652+
contentValues.putAll(props);
653+
601654
if (dataWriter != null) {
602-
setFileContent(contentValues, mimeType, dataWriter);
655+
final PipedInputStream pis = new PipedInputStream();
656+
Executors.newSingleThreadExecutor().submit(() -> {
657+
try (PipedOutputStream pos = new PipedOutputStream(pis)) {
658+
dataWriter.accept(pos);
659+
} catch (Exception e) {
660+
throw new RepoException(String.format("Cannot write data to file at path '%s'!", path), e);
661+
}
662+
});
663+
contentValues.put(JcrConstants.JCR_DATA, pis);
603664
} else {
604-
setFileContent(contentValues, mimeType, data);
665+
contentValues.put(JcrConstants.JCR_DATA, data);
605666
}
606667
}
607668

608-
private void setFileContent(Map<String, Object> contentValues, String mimeType, Object data) {
609-
contentValues.put(JcrConstants.JCR_PRIMARYTYPE, JcrConstants.NT_RESOURCE);
610-
contentValues.put(JcrConstants.JCR_ENCODING, "utf-8");
611-
contentValues.put(JcrConstants.JCR_MIMETYPE, mimeType);
612-
contentValues.put(JcrConstants.JCR_DATA, data);
613-
}
614-
615-
// https://stackoverflow.com/a/27172165
616-
private void setFileContent(Map<String, Object> contentValues, String mimeType, Consumer<OutputStream> dataWriter) {
617-
contentValues.put(JcrConstants.JCR_PRIMARYTYPE, JcrConstants.NT_RESOURCE);
618-
contentValues.put(JcrConstants.JCR_ENCODING, "utf-8");
619-
contentValues.put(JcrConstants.JCR_MIMETYPE, mimeType);
620-
final PipedInputStream pis = new PipedInputStream();
621-
Executors.newSingleThreadExecutor().submit(() -> {
622-
try (PipedOutputStream pos = new PipedOutputStream(pis)) {
623-
dataWriter.accept(pos);
624-
} catch (Exception e) {
625-
throw new RepoException(String.format("Cannot write data to file at path '%s'!", path), e);
626-
}
627-
});
628-
contentValues.put(JcrConstants.JCR_DATA, pis);
629-
}
630-
631669
public InputStream readFileAsStream() {
632670
Resource resource = require();
633671
Resource contentResource = resource.getChild(JcrConstants.JCR_CONTENT);

core/src/main/java/dev/vml/es/acm/core/script/Script.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22

33
import com.fasterxml.jackson.annotation.JsonIgnore;
44
import dev.vml.es.acm.core.AcmException;
5-
import dev.vml.es.acm.core.code.ExecutableMetadata;
65
import dev.vml.es.acm.core.code.Executable;
6+
import dev.vml.es.acm.core.code.ExecutableMetadata;
77
import java.io.IOException;
88
import java.io.InputStream;
99
import java.nio.charset.StandardCharsets;

core/src/main/java/dev/vml/es/acm/core/script/ScriptRepository.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77
import dev.vml.es.acm.core.repo.RepoException;
88
import dev.vml.es.acm.core.repo.RepoResource;
99
import dev.vml.es.acm.core.util.ResourceSpliterator;
10+
import java.io.ByteArrayInputStream;
11+
import java.io.InputStream;
12+
import java.nio.charset.StandardCharsets;
1013
import java.util.List;
1114
import java.util.Optional;
1215
import java.util.stream.Stream;
@@ -55,10 +58,10 @@ private Resource getRoot(ScriptType type) throws RepoException {
5558
}
5659

5760
public Script save(Code code) {
58-
return save(code.getId(), code.getContent());
61+
return save(code.getId(), new ByteArrayInputStream(code.getContent().getBytes(StandardCharsets.UTF_8)));
5962
}
6063

61-
public Script save(String id, Object data) throws AcmException {
64+
public Script save(String id, InputStream data) throws AcmException {
6265
if (!ScriptType.byPath(id).isPresent()) {
6366
throw new AcmException(String.format("Cannot save script '%s' at unsupported path!", id));
6467
}

test/e2e/002-console.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ test.describe('Console', () => {
2525
await page.getByRole('tab', { name: 'Output' }).click();
2626
await expectExecutionProgressBarSucceeded(page);
2727

28-
const output = await readFromCodeEditor(page);
28+
const output = await readFromCodeEditor(page, 'Console Output');
2929
expectToHaveMultilineText(output, `
3030
Hello World!
3131
`);

test/e2e/003-tool-access.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ test.describe('Tool Access', () => {
6363
await page.getByRole('tab', { name: 'Output' }).click();
6464
await expectExecutionProgressBarSucceeded(page);
6565

66-
const output = await readFromCodeEditor(page);
66+
const output = await readFromCodeEditor(page, 'Console Output');
6767
expect(output).toContain('Setup complete!');
6868

6969
await newAemContext(browser, 'acm-test-user', 'test1234', async (testUserPage) => {

test/e2e/004-history.spec.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ test.describe('History', () => {
1717
await expect(firstRow.locator('[role="rowheader"]')).toContainText('Console');
1818
await firstRow.click();
1919
await page.getByRole('tab', { name: 'Output' }).click();
20-
const firstOutput = await readFromCodeEditor(page);
20+
const firstOutput = await readFromCodeEditor(page, 'Execution Output');
2121
expect(firstOutput).toContain('Setup complete!');
2222

2323
await page.goto('/acm#/history');
@@ -27,7 +27,24 @@ test.describe('History', () => {
2727
await expect(secondRow.locator('[role="rowheader"]')).toContainText('Console');
2828
await secondRow.click();
2929
await page.getByRole('tab', { name: 'Output' }).click();
30-
const secondOutput = await readFromCodeEditor(page);
30+
const secondOutput = await readFromCodeEditor(page, 'Execution Output');
3131
expect(secondOutput).toContain('Hello World!');
3232
});
33+
34+
test('Shows automatic script executions', async ({ page }) => {
35+
await page.goto('/acm');
36+
await page.getByRole('button', { name: 'History' }).click();
37+
38+
const grid = page.locator('[role="grid"][aria-label="Executions table"]');
39+
await expect(grid).toBeVisible();
40+
const rows = grid.locator('[role="row"]');
41+
42+
await page.getByRole('searchbox', { name: 'Executable' }).fill('example/ACME-20_once');
43+
await expect(rows.nth(1)).toContainText('Script \'example/ACME-20_once\'');
44+
await expect(rows.nth(1)).toContainText('succeeded');
45+
46+
await page.getByRole('searchbox', { name: 'Executable' }).fill('example/ACME-21_changed');
47+
await expect(rows.nth(1)).toContainText('Script \'example/ACME-21_changed\'');
48+
await expect(rows.nth(1)).toContainText('succeeded');
49+
});
3350
});

0 commit comments

Comments
 (0)