ls command to list content of directories, and a cat command to list files content.

QueryParameter_. This equips us with a generic and unified mechanism of parsing any arbitrary list of query parameters and enabling access to them at later processing steps in the iFlow.
import com.sap.gateway.ip.core.customdev.util.Message
import java.nio.charset.StandardCharsets
Message parse(Message message) {
String query = message.getHeader('CamelHttpQuery', String)
query = (query) ? URLDecoder.decode(query, StandardCharsets.UTF_8.name()) : ''
Map<String, String> queryParameters = query.tokenize('&').collectEntries { entry ->
entry.tokenize('=').toSpreadMap().collectEntries { parameter ->
['QueryParameter_' + parameter.key, parameter.value]
}
}
message.setProperties(queryParameters)
return message
}path – that is mandatory and common for all of them – a file/directory path to an object that an operation shall be applied to. Therefore, we would like to have an early check that the mandatory parameter was provided in the request. I use a router step for this: if the required query parameter (and consequently, a corresponding exchange property) is missing, the iFlow produces a message with a response status code 400 (Bad Request):




| HTTP method | Query parameters | Operation | Groovy function |
| HEAD | path | Check if file exists | check |
| GET | path | Download file | download |
| POST | path, mode, permissions | Upload file | upload |
| DELETE | path | Delete file | delete |
| VIEW | path, output | Browse directory | list |

path parameter for a file operation, an error will be returned in a response message.~/ – that is used to denote a home directory of a currently logged user. In fact, there are several kinds of expansion that a shell performs and the above mentioned ~/ is one of examples of a tilde expansion. A comprehensive description of shell expansions that are supported by a Bash shell in Ubuntu that is used by a runtime node currently, can be found in a corresponding documentation. There is no equivalent functionality offered by JDK/GDK APIs, as shell expansions are a feature that is specific to a shell, not to underlying system APIs, and this demo doesn’t aim replication of shell expansions logic to its full extent, but given ~/ is used so often, I added a support for it here. Since this functionality is going to be used by all operations, logic that is required to expand ~/ has been moved to a separate function in the script – expandPath(String):private static String expandPath(String path) {
// Supports only tilde expansion for current user's home directory
return path?.replaceAll('~/', System.getProperty('user.home') + '/')
}
path parameter, exists on a file system of a runtime node.import com.sap.gateway.ip.core.customdev.util.Message
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
Message check(Message message) {
Path file = Paths.get(expandPath(message.getProperty('QueryParameter_path') as String))
try {
if (Files.exists(file) && Files.isRegularFile(file)) {
message.setHeader('CamelHttpResponseCode', 200)
} else {
message.setHeader('CamelHttpResponseCode', 404)
}
} catch (Exception e) {
message.setHeader('CamelHttpResponseCode', 500)
}
return message
}


path parameter, if such file exists.import com.sap.gateway.ip.core.customdev.util.Message
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
Message download(Message message) {
Path file = Paths.get(expandPath(message.getProperty('QueryParameter_path') as String))
try {
if (Files.exists(file) && Files.isRegularFile(file) && Files.isReadable(file)) {
byte[] content = Files.readAllBytes(file)
message.setBody(content)
message.setHeaders([
'CamelHttpResponseCode': 200,
'Content-Disposition' : "inline; filename=\"${file.fileName.toString()}\"",
'Content-Type' : 'application/octet-stream'
])
} else {
message.setBody("File ${file.toAbsolutePath().toString()} does not exist or is not readable")
message.setHeaders([
'CamelHttpResponseCode': 500,
'Content-Type' : 'text/plain'
])
}
} catch (Exception e) {
message.setBody("Error when reading file ${file.toAbsolutePath().toString()}: ${e.message}")
message.setHeaders([
'CamelHttpResponseCode': 500,
'Content-Type' : 'text/plain'
])
}
return message
}


path parameter.mode parameter is evaluated:overwrite, existing file content is overwritten with uploaded content,append, uploaded content is appended to existing file content. This option shall be used with caution: it shall not be applied to binary content or text content in structured data formats (XML, JSON, etc.), as this may cause inconsistencies in joint content (malformed content),skip, an upload operation is skipped and file content remains unmodified.skip).rw-r--r-- in a symbolic notation, or 644 in a numeric (octal) notation (read and write permissions for the owner, read permission for all other users). There are certain use cases when default permissions aren’t desirable and the file has to be created with some other (more or less restrictive) permissions. For such cases, a permissions parameter can be used: if it is provided and its value is a valid representation of file permissions in either symbolic or numeric notation, corresponding permissions will be assigned to the created/updated file. To retain readability of the function that is invoked for upload of the file, logic that is required to parse the value of the permissions parameter, has been moved to a separate function in the script – parseFilePermissions(String):import java.nio.file.attribute.PosixFilePermission
import java.nio.file.attribute.PosixFilePermissions
private static Set<PosixFilePermission> parseFilePermissions(String value) {
Set<PosixFilePermission> permissions = []
switch (value) {
case { value ==~ /^([-r][-w][-x]){3}$/ }: // Symbolic notation
permissions = PosixFilePermissions.fromString(value)
break
case { value ==~ /^[01234567]{3}$/ }: // Numeric notation
value.eachWithIndex { triad, triadIdx ->
Integer.toBinaryString(triad.toInteger()).padLeft(3, '0').eachWithIndex { String bit, int bitIdx ->
if (bit.toInteger()) {
if (triadIdx == 0 && bitIdx == 0) permissions.add(PosixFilePermission.OWNER_READ)
if (triadIdx == 0 && bitIdx == 1) permissions.add(PosixFilePermission.OWNER_WRITE)
if (triadIdx == 0 && bitIdx == 2) permissions.add(PosixFilePermission.OWNER_EXECUTE)
if (triadIdx == 1 && bitIdx == 0) permissions.add(PosixFilePermission.GROUP_READ)
if (triadIdx == 1 && bitIdx == 1) permissions.add(PosixFilePermission.GROUP_WRITE)
if (triadIdx == 1 && bitIdx == 2) permissions.add(PosixFilePermission.GROUP_EXECUTE)
if (triadIdx == 2 && bitIdx == 0) permissions.add(PosixFilePermission.OTHERS_READ)
if (triadIdx == 2 && bitIdx == 1) permissions.add(PosixFilePermission.OTHERS_WRITE)
if (triadIdx == 2 && bitIdx == 2) permissions.add(PosixFilePermission.OTHERS_EXECUTE)
}
}
}
break
// default: // Invalid value
}
return permissions
}import com.sap.gateway.ip.core.customdev.util.Message
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
import java.nio.file.StandardOpenOption
import java.nio.file.attribute.PosixFilePermission
Message upload(Message message) {
Path file = Paths.get(expandPath(message.getProperty('QueryParameter_path') as String))
String mode = (message.getProperty('QueryParameter_mode') as String)?.toLowerCase()
Set<PosixFilePermission> permissions = parseFilePermissions(message.getProperty('QueryParameter_permissions') as String)
byte[] content = message.getBody(byte[])
Path directory = file.parent
try {
if (Files.exists(directory) && Files.isDirectory(directory) && Files.isWritable(directory)) {
if (Files.exists(file) && Files.isRegularFile(file)) {
switch (mode) {
case 'overwrite':
Files.write(file, content, StandardOpenOption.TRUNCATE_EXISTING)
if (permissions) {
Files.setPosixFilePermissions(file, permissions)
}
message.setBody("File ${file.toAbsolutePath().toString()} already exists, content was overwritten")
break
case 'append':
Files.write(file, content, StandardOpenOption.APPEND)
if (permissions) {
Files.setPosixFilePermissions(file, permissions)
}
message.setBody("File ${file.toAbsolutePath().toString()} already exists, content was appended")
break
case 'skip':
message.setBody("File ${file.toAbsolutePath().toString()} already exists, upload was skipped")
break
default:
message.setBody("File ${file.toAbsolutePath().toString()} already exists, upload was skipped")
}
} else {
Files.write(file, content)
if (permissions) {
Files.setPosixFilePermissions(file, permissions)
}
message.setBody("File ${file.toAbsolutePath().toString()} was uploaded")
}
message.setHeaders([
'CamelHttpResponseCode': 200,
'Content-Type' : 'text/plain'
])
} else {
message.setBody("Directory ${directory.toAbsolutePath().toString()} does not exist or is not writable")
message.setHeaders([
'CamelHttpResponseCode': 500,
'Content-Type' : 'text/plain'
])
}
} catch (Exception e) {
message.setBody("Error when creating file ${file.toAbsolutePath().toString()}: ${e.message}")
message.setHeaders([
'CamelHttpResponseCode': 500,
'Content-Type' : 'text/plain'
])
}
return message
}




path parameter, if such file exists.import com.sap.gateway.ip.core.customdev.util.Message
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
Message delete(Message message) {
Path file = Paths.get(expandPath(message.getProperty('QueryParameter_path') as String))
try {
if (Files.exists(file) && Files.isRegularFile(file)) {
Files.delete(file)
message.setBody("File ${file.toAbsolutePath().toString()} was deleted")
message.setHeaders([
'CamelHttpResponseCode': 200,
'Content-Type' : 'text/plain'
])
} else {
message.setBody("File ${file.toAbsolutePath().toString()} does not exist")
message.setHeaders([
'CamelHttpResponseCode': 500,
'Content-Type' : 'text/plain'
])
}
} catch (Exception e) {
message.setBody("Error when deleting file ${file.toAbsolutePath().toString()}: ${e.message}")
message.setHeaders([
'CamelHttpResponseCode': 500,
'Content-Type' : 'text/plain'
])
}
return message
}
path parameter, including hidden directories and files./ symbol. Following sorting order is applied to entries that are listed in the output:.) are listed, followed by other directories and files,path parameter might contain a relative path, while it might be also useful to get to now an absolute path for it, a response message contains an additional X-Directory header that indicates an absolute path to a listed directory.output parameter can be used: if it is provided and its value is equal to verbose, an output will be verbose and will include additional details about listed directories and files – such as owner, group, permissions, size, last modified time. In a verbose mode, an output might not be easily readable due to absence of proper output formatting (an output is produced as plain text and lacks columns alignment, text colouring, etc.), but since it is a pipe-delimited output, it can be copied from or saved a response message and then imported, parsed and analyzed in tools that are capable of handling CSV-like data formats.import com.sap.gateway.ip.core.customdev.util.Message
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
import java.nio.file.attribute.PosixFileAttributes
import java.nio.file.attribute.PosixFilePermissions
import java.time.format.DateTimeFormatter
import java.util.stream.Collectors
Message list(Message message) {
Path directory = Paths.get(expandPath(message.getProperty('QueryParameter_path') as String))
boolean isOutputVerbose = ((message.getProperty('QueryParameter_output') as String)?.toLowerCase() == 'verbose')
try {
if (Files.exists(directory) && Files.isDirectory(directory)) {
StringBuilder builder = new StringBuilder()
List<Path> entries = Files.walk(directory, 1).skip(1).collect(Collectors.toList())
if (entries) {
if (isOutputVerbose) {
builder.append(new StringJoiner('|', '', '\n').with {
add('Owner')
add('Group')
add('Permissions')
add('Size (bytes)')
add('Last modified time')
add('Name')
}.toString())
}
entries
.toSorted(new OrderBy([
{ Path entry -> entry.fileName.toString().startsWith('.') },
{ Path entry -> Files.isDirectory(entry) },
{ Path entry -> !entry.fileName.toString() }
]).reversed())
.each { Path entry ->
PosixFileAttributes attributes = Files.readAttributes(entry, PosixFileAttributes)
StringJoiner line = new StringJoiner('|', '', '\n')
if (isOutputVerbose) {
line.with {
add(attributes.owner().name)
add(attributes.group().name)
add(PosixFilePermissions.toString(attributes.permissions()))
add(attributes.size().toString())
add(DateTimeFormatter.ISO_INSTANT.format(attributes.lastModifiedTime().toInstant()))
}
}
line.add(entry.fileName.toString() + (attributes.directory ? '/' : ''))
builder.append(line.toString())
}
}
message.setBody(builder.toString())
message.setHeaders([
'CamelHttpResponseCode': 200,
'Content-Type' : 'text/plain',
'X-Directory' : directory.toAbsolutePath().toString()
])
} else {
message.setBody("Directory ${directory.toAbsolutePath().toString()} does not exist")
message.setHeaders([
'CamelHttpResponseCode': 500,
'Content-Type' : 'text/plain'
])
}
} catch (Exception e) {
message.setBody("Error when listing directory ${directory.toAbsolutePath().toString()}: ${e.message}")
message.setHeaders([
'CamelHttpResponseCode': 500,
'Content-Type' : 'text/plain'
])
}
return message
}X-Directory header:



You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
| User | Count |
|---|---|
| 27 | |
| 24 | |
| 20 | |
| 19 | |
| 13 | |
| 13 | |
| 12 | |
| 12 | |
| 12 | |
| 11 |