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
2 changes: 1 addition & 1 deletion core/src/main/java/dev/vml/es/acm/core/code/Executor.java
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ private ContextualExecution executeInternal(ExecutionContext context) {
useLocker(resolverFactory, l -> l.lock(lockName));
}
context.notifyStatus(ExecutionStatus.RUNNING);
if (config.logPrintingEnabled()) {
if (!healthChecking && config.logPrintingEnabled()) {
context.getOut().fromSelfLogger();
context.getOut().fromLoggers(config.logPrintingNames());
context.getOut().setLoggerTimestamps(config.logPrintingTimestamps());
Expand Down
7 changes: 6 additions & 1 deletion core/src/main/java/dev/vml/es/acm/core/code/FileManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,15 @@

public class FileManager {

public static final String ROOT = AcmConstants.VAR_ROOT + "/file";

private Repo repo;

private RepoResource root;

public FileManager(ResourceResolver resolver) {
this.repo = Repo.quiet(resolver);
this.root = repo.get(AcmConstants.VAR_ROOT + "/file");
this.root = repo.get(ROOT);
}

public Optional<String> find(String path) {
Expand Down Expand Up @@ -64,6 +66,9 @@ public List<String> deleteAll(List<String> paths) {
}

public String delete(String path) {
if (path.startsWith("/") && !path.startsWith(ROOT + "/")) {
throw new AcmException(String.format("File is outside managed root and cannot be deleted '%s'!", path));
}
RepoResource resource = findResource(path);
if (resource == null) {
throw new AcmException(String.format("File to be deleted does not exist '%s'!", path));
Expand Down
15 changes: 15 additions & 0 deletions core/src/main/java/dev/vml/es/acm/core/code/TextOutput.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package dev.vml.es.acm.core.code;

import java.util.Collection;
import java.util.Map;
import java.util.stream.Collectors;

Expand Down Expand Up @@ -49,4 +50,18 @@ public String links(Map<String, String> links) {
.map(entry -> "- [" + entry.getKey() + "](" + entry.getValue() + ")")
.collect(Collectors.joining(System.lineSeparator()));
}

public String lines(String[] lines) {
return String.join(System.lineSeparator(), lines);
}

public String lines(Collection<String> lines) {
return String.join(System.lineSeparator(), lines);
}

public String lines(Map<String, ?> values) {
return values.entrySet().stream()
.map(entry -> "- **" + entry.getKey() + ":** " + entry.getValue())
.collect(Collectors.joining(System.lineSeparator()));
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package dev.vml.es.acm.core.code.input;

import dev.vml.es.acm.core.AcmException;
import dev.vml.es.acm.core.code.FileManager;
import dev.vml.es.acm.core.code.Input;
import dev.vml.es.acm.core.code.InputType;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.apache.commons.lang3.StringUtils;

abstract class AbstractFileInput<V> extends Input<V> {

Expand All @@ -14,6 +17,15 @@ public AbstractFileInput(String name, InputType type, Class<?> valueType) {
super(name, type, valueType);
}

protected void validatePath(String path) {
if (StringUtils.isNotBlank(path) && !path.startsWith(FileManager.ROOT + "/")) {
throw new AcmException(String.format(
Comment thread
krystian-panek-vmltech marked this conversation as resolved.
"File input '%s' cannot have a value '%s' pointing outside ACM file storage '%s'. "
+ "For selecting files from DAM or other repository locations, use path input instead.",
getName(), path, FileManager.ROOT));
}
}

public List<String> getMimeTypes() {
return mimeTypes;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,10 @@ public class FileInput extends AbstractFileInput<String> {
public FileInput(String name) {
super(name, InputType.FILE, String.class);
}

@Override
public void setValue(String value) {
validatePath(value);
super.setValue(value);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,16 @@ public MultiFileInput(String name) {
super(name, InputType.MULTIFILE, String[].class);
}

@Override
public void setValue(String[] values) {
if (values != null) {
for (String value : values) {
validatePath(value);
}
}
super.setValue(values);
}

public Integer getMin() {
return min;
}
Expand Down
44 changes: 36 additions & 8 deletions core/src/main/java/dev/vml/es/acm/core/repo/RepoResource.java
Original file line number Diff line number Diff line change
Expand Up @@ -667,15 +667,11 @@ private void setFileContent(
}

public InputStream readFileAsStream() {
Resource resource = require();
Resource contentResource = resource.getChild(JcrConstants.JCR_CONTENT);
if (contentResource == null) {
throw new RepoException(String.format("Cannot read file at path '%s' as it does not have content!", path));
}
InputStream inputStream = contentResource.adaptTo(InputStream.class);
RepoResource fileResource = requireFile();
InputStream inputStream = fileResource.resolve().adaptTo(InputStream.class);
if (inputStream == null) {
throw new RepoException(
String.format("Cannot read file at path '%s' as it does not have content stream!", path));
String.format("Cannot read file at path '%s' as it does not have binary data!", path));
}
return inputStream;
}
Expand All @@ -688,8 +684,40 @@ public String readFileAsString() {
}
}

public RepoResource resolveFile() {
return file().orElse(null);
}

public RepoResource requireFile() {
return file().orElseThrow(() ->
new RepoException(String.format("Resource at path '%s' does not have file content!", path)));
}

private Optional<RepoResource> file() {
Comment thread
krystian-panek-vmltech marked this conversation as resolved.
Resource resource = resolve();
if (resource == null) {
return Optional.empty();
}
Resource contentResource = resource.getChild(JcrConstants.JCR_CONTENT);
if (contentResource == null) {
return Optional.empty();
}
if (path.startsWith(AemConstants.DAM_ROOT + "/")) {
String damOriginalPath = AemConstants.DAM_RENDITION_FOLDER + "/" + AemConstants.DAM_RENDITION_ORIGINAL + "/"
+ JcrConstants.JCR_CONTENT;
Resource damContent = contentResource.getChild(damOriginalPath);
if (damContent != null && JcrUtils.hasBinaryData(damContent.adaptTo(Node.class))) {
return Optional.of(new RepoResource(repo, damContent.getPath()));
}
}
if (JcrUtils.hasBinaryData(contentResource.adaptTo(Node.class))) {
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this simply behaves like Sling adapter for InputStream ; but unfortunately the original method is internal to Sling to copied-adapted to ACM to have 1-to-1 compatibility

return Optional.of(new RepoResource(repo, contentResource.getPath()));
}
return Optional.empty();
}

public boolean isFile() {
return isType(JcrConstants.NT_FILE);
return file().isPresent();
}

public String type() {
Expand Down
14 changes: 14 additions & 0 deletions core/src/main/java/dev/vml/es/acm/core/util/AemConstants.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package dev.vml.es.acm.core.util;

public final class AemConstants {

public static final String DAM_ROOT = "/content/dam";

public static final String DAM_RENDITION_FOLDER = "renditions";

public static final String DAM_RENDITION_ORIGINAL = "original";

private AemConstants() {
// constants only
}
}
47 changes: 47 additions & 0 deletions core/src/main/java/dev/vml/es/acm/core/util/JcrUtils.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
package dev.vml.es.acm.core.util;

import static javax.jcr.Property.JCR_CONTENT;
import static javax.jcr.Property.JCR_DATA;
import static javax.jcr.Property.JCR_FROZEN_PRIMARY_TYPE;
import static javax.jcr.nodetype.NodeType.NT_FILE;
import static javax.jcr.nodetype.NodeType.NT_FROZEN_NODE;
import static javax.jcr.nodetype.NodeType.NT_LINKED_FILE;

import java.util.Iterator;
import java.util.stream.Stream;
import javax.jcr.Item;
import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.Property;
import javax.jcr.RepositoryException;

public class JcrUtils {

Expand All @@ -23,4 +33,41 @@ public Node next() {
}
});
}

/**
* Returns the binary data property of the given node (e.g. for nt:file, nt:linkedFile, dam:Asset).
* Copied from internal Sling class.
*
* @see <a href="https://github.com/apache/sling-org-apache-sling-jcr-resource/blob/master/src/main/java/org/apache/sling/jcr/resource/internal/NodeUtil.java">NodeUtil#getPrimaryProperty</a>
*/
public static Property getBinaryDataProperty(Node node) throws RepositoryException {
Node content = (node.isNodeType(NT_FILE)
|| (node.isNodeType(NT_FROZEN_NODE)
&& node.getProperty(JCR_FROZEN_PRIMARY_TYPE)
.getString()
.equals(NT_FILE)))
? node.getNode(JCR_CONTENT)
: node.isNodeType(NT_LINKED_FILE)
? node.getProperty(JCR_CONTENT).getNode()
: node;
if (content.hasProperty(JCR_DATA)) {
return content.getProperty(JCR_DATA);
}
Item item = content.getPrimaryItem();
while (item.isNode()) {
item = ((Node) item).getPrimaryItem();
}
return (Property) item;
}

public static boolean hasBinaryData(Node node) {
if (node == null) {
return false;
}
try {
return getBinaryDataProperty(node) != null;
} catch (RepositoryException e) {
return false;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import org.apache.poi.ss.usermodel.*
import org.apache.poi.xssf.usermodel.XSSFWorkbook
import java.time.LocalDate
import java.time.ZoneId

/*
---
author: <john.doe@acme.com>
---
Reads an XLSX file (e.g. generated by ACME-203) and outputs summary statistics.
Demonstrates how to use file input for uploading and parsing spreadsheet files.

```mermaid
graph TD
A[Upload XLS File] --> B[Open Workbook]
B --> C[Read Sheet]
C --> D[Parse Rows]
D --> E[Calculate Statistics]
E --> F[Output Summary]
```
*/

void describeRun() {
inputs.file("xlsFile") {
label = "XLS/XLSX File"
description = "Upload a spreadsheet file to analyze (e.g. report.xlsx from ACME-203)"
mimeTypes = [
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"application/vnd.ms-excel"
]
}
}

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

void doRun() {
out.info "Reading XLS file..."

def xlsFile = repo.get(inputs.value("xlsFile"))

try {
Workbook workbook = new XSSFWorkbook(xlsFile.readFileAsStream())
Sheet sheet = workbook.getSheetAt(0)

// Build column name -> index map from header row
Row headerRow = sheet.getRow(0)
def columns = [:]
for (Cell cell : headerRow) {
columns[cell.getStringCellValue()] = cell.getColumnIndex()
}
out.info "Found columns: ${columns.keySet().join(', ')}"

def rowCount = 0
def names = [] as Set
def surnames = [] as Set
def oldestDate = null
def youngestDate = null

for (Row row : sheet) {
if (row.getRowNum() == 0) continue // skip header

context.checkAborted()
rowCount++

def nameCell = row.getCell(columns["Name"])
def surnameCell = row.getCell(columns["Surname"])
def dateCell = row.getCell(columns["Birth Date"])

if (nameCell) names.add(nameCell.getStringCellValue())
if (surnameCell) surnames.add(surnameCell.getStringCellValue())

if (dateCell) {
def date = null
if (DateUtil.isCellDateFormatted(dateCell)) {
date = dateCell.getDateCellValue().toInstant().atZone(ZoneId.systemDefault()).toLocalDate()
} else if (dateCell.getCellType() == CellType.STRING) {
date = LocalDate.parse(dateCell.getStringCellValue())
}
if (date != null) {
if (oldestDate == null || date.isBefore(oldestDate)) oldestDate = date
if (youngestDate == null || date.isAfter(youngestDate)) youngestDate = date
}
}

if (rowCount % 1000 == 0) out.info("Processed ${rowCount} rows...")
}

workbook.close()

outputs.text("summary") {
label = "Summary"
value = lines([
"Rows processed": rowCount,
"Unique names": names.size(),
"Unique surnames": surnames.size(),
"Oldest birth date": oldestDate ?: 'N/A',
"Youngest birth date": youngestDate ?: 'N/A'
])
}

out.success "XLS file analysis completed"
} finally {
xlsFile.delete()
}
}