#
# need to use source inclusion at the api level, since plumber
# loads this script and creates the api. any functions / variables
# or else defined at package level is not visible here elsewise.
#
#' @include Collection-class.R
#' @include Product-class.R
#' @include Job-class.R
#' @include processes.R
#' @include data.R
#' @include Server-class.R
#' @include api_data.R
#' @include api_job.R
#' @include api_users.R
#' @include api_services.R
#' @include api_processes.R
#' @include api_credentials.R
#' @include api_process_graphs.R
#' @include FilterableEndpoint.R
#' @include errors.R
#' @import raster
#' @import plumber
#' @importFrom sodium data_encrypt
#' @importFrom sodium bin2hex
#' @importFrom sodium data_decrypt
#' @importFrom sodium hex2bin
#' @importFrom jsonlite base64_dec
#' @importFrom jsonlite base64_enc
#
# serverinformation endpoint ====
#
.capabilities = function() {
endpoints = openeo.server$getEndpoints()
endpoints = endpoints %>% group_by(path) %>% summarise(
path_capabilities=list(tibble(path,method) %>% (function(x,...){
return(list(path=unique(x$path),methods=as.list(x$method)))
}))
)
list(
version = openeo.server$configuration$api.version,
endpoints = endpoints$path_capabilities,
billing = list(
currency = "EUR",
plans = list(
list(
name="free",
description = "Free. Unlimited calculations, no credit use. Its a test system!",
url="http://openeo.org/plans/free-plan"
)
)
)
)
}
.output_formats = function() {
raster.only.formats = openeo.server$configuration$outputGDALFormats[which(! openeo.server$configuration$outputGDALFormats %in% openeo.server$configuration$outputOGRFormats)]
rasterformats = lapply(raster.only.formats, function(formatname) {
format = list(
gis_data_types = c("raster")
)
return(format)
})
names(rasterformats) = raster.only.formats
vector.only.formats = openeo.server$configuration$outputOGRFormats[which(! openeo.server$configuration$outputOGRFormats %in% openeo.server$configuration$outputGDALFormats)]
vectorformats = lapply(vector.only.formats, function(formatname) {
format = list(
gis_data_types = c("vector")
)
return(format)
})
names(vectorformats) = vector.only.formats
both.type.formats = openeo.server$configuration$outputOGRFormats[which(openeo.server$configuration$outputOGRFormats %in% openeo.server$configuration$outputGDALFormats)]
bothformats = lapply(both.type.formats, function(formatname) {
format = list(
gis_data_types = c("raster","vector"),
parameters = list(gis_data_type = list(
type = "string",
enum = c("raster","vector"),
required = TRUE
)
)
)
return(format)
})
names(bothformats) = both.type.formats
formats = c(rasterformats,vectorformats,bothformats)
return(list(
default="GTiff",
formats = formats
))
}
.services = function() {
# TODO also list WFS and WCS here
return(list(
WMS = list(
parameters=list(
version=list(
type="string",
description="The WMS version that has to be used.",
default="1.3.0",
enum=c("1.1.1","1.3.0")
)
),
attributes = list(
layers=list(
type="array",
description="Array of layer names.",
example=c("b01","b02","ndvi")
)
)
)
))
}
.udf_runtimes = function() {
return(lapply(openeo.server$udf_runtimes, function(x){
x=unclass(x)
x$performTransaction = NULL
return(x)
} ))
}
# /preview endpoint ----
.executeSynchronous = function(req,res,format=NULL) {
tryCatch({
if (!is.null(req$postBody)) {
sent_job = fromJSON(req$postBody,simplifyDataFrame = FALSE)
output = sent_job$output
process_graph = sent_job$process_graph
if (is.null(format)) {
format = output$format
}
if (is.null(format) ||
!(format %in% openeo.server$configuration$outputGDALFormats ||
format %in% openeo.server$configuration$outputOGRFormats)) {
throwError("FormatUnsupported")
}
} else {
throwError("ContentTypeInvalid",types="application/json")
}
job = Job$new(process_graph=process_graph,user_id = req$user$user_id)
job$output = output
job$job_id = syncJobId()
openeo.server$runJob(job = job, format = format, response = TRUE, res = res)
job$clearLog()
return(res)
},error=handleError,
finally = {
removeJobsUdfData(job)
})
}
.ogrExtension = function(format) {
metadata <- system(paste("ogrinfo --format ","\"",format,"\"",sep=""), intern=TRUE)
extension.line = sapply(metadata, function(line) {
m = regexec("\\s*Extension[s]?:\\s*([a-zA-Z0-9\\-_]*).*$",line)
split = unlist(regmatches(line, m))
if (length(split) > 0) {
return(split[2])
} else {
return(NULL)
}
},USE.NAMES = FALSE)
return(unlist(extension.line))
}
# creates files for batch processing
.create_output_no_response = function(result, format, dir,logger) {
result$toFile(dir=dir,format=format,logger=logger)
}
# creates file output for a direct webservice result (executeSynchronous)
.create_output = function(res, result, format, logger) {
if (is.null(result)) {
logger$error("Outputter did not receive a collection for output.")
}
if (result$dimensions$feature) {
contentType = paste("application/x-ogr-",format,sep="")
} else {
contentType = paste("application/x-gdal-",format,sep="")
}
tryCatch({
temp = result$toFile(format=format, temp=TRUE, logger=logger)
first = temp$getData()$output.file[[1]]
sendFile(res,
status=200,
file.name="output",
contentType=contentType,
data=readBin(first, "raw", n=file.info(first)$size))
},error=function(e){
logger$error(e$message)
},finally = {
if (!is.null(temp)) {
unlink(temp$getData()$output.file)
}
})
}
#
# pipeline filter ====
#
#* @filter checkAuth
.authorized = function(req, res){
tryCatch({
if (req$REQUEST_METHOD == 'OPTIONS') {
return(forward())
}
auth = unlist(strsplit(req$HTTP_AUTHORIZATION," "))
if (auth[1] == "Bearer") {
token = auth[2]
hextoken = hex2bin(token)
nonce.length = 24
msg = hextoken[1:(length(hextoken)-nonce.length)]
nonce = hextoken[((length(hextoken)-nonce.length)+1):length(hextoken)]
user_id = rawToChar(data_decrypt(msg,openeo.server$configuration$secret.key,nonce))
user = User$new()
user = user$load(user_id = user_id)
req$user = user
forward()
} else {
throwError("AuthenticationSchemeInvalid")
}
}, error=handleError)
}
.validateProcessGraphFilter = function(req, res, ...) {
tryCatch({
parsedGraph = fromJSON(req$postBody,simplifyDataFrame = FALSE)
is_process_graph_set = "process_graph" %in% names(parsedGraph)
if (is_process_graph_set) {
process_graph = ProcessGraph$new(process_graph = parsedGraph[["process_graph"]])
} else {
if (req$REQUEST_METHOD == "POST") {
throwError("ProcessGraphMissing")
} else {
# the other option is PATCH, but there we don't require process_graph
forward()
}
}
tryCatch({
process_graph$buildExecutableProcessGraph(user = req$user)
forward()
},error=function(e) {
# TODO improve this further
throwError("ProcessGraphMissing")
})
}, error=handleError)
}
.cors_filter = function(req,res) {
res$setHeader("Access-Control-Allow-Origin", req$HTTP_ORIGIN)
res$setHeader("Access-Control-Allow-Credentials", "true")
res$setHeader("Access-Control-Expose-Headers", "OpenEO-Identifier, OpenEO-Costs")
forward()
}
.cors_option_bypass = function(req,res, ...) {
res$setHeader("Access-Control-Allow-Headers", "Authorization, Accept, Content-Type")
res$setHeader("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,OPTIONS,PATCH")
res$status = 200
}
.not_implemented_yet = function(req,res, ...) {
error(res,501, "The back-end responds with this error code whenever an endpoint is specified in the openEO API, but is not supported.", code = 501)
}
#
# utility functions ====
#
# @deprecated in the next time
error = function(res, status,msg, code = NULL) {
res$status = status
# id and links are spared for now
return(list(
code = code,
message=msg,
links = list())
)
}
sendFile = function(res, status, file.name = NA,file.ext=NA, contentType=NA, data) {
res$status = status
res$body = data
if (! is.na(contentType)) {
res$setHeader("Content-Type", contentType)
}
if (! is.na(file.name) && !is.na(file.ext)) {
res$setHeader("Content-Disposition", paste("attachment; filename=",file.name,file.ext,sep=""))
}
return(res)
}
.isURL = function(url) {
pattern= "^(?:(?:http(?:s)?|ftp)://)(?:\\S+(?::(?:\\S)*)?@)?(?:(?:[a-z0-9\u00a1-\uffff](?:-)*)*(?:[a-z0-9\u00a1-\uffff])+)(?:\\.(?:[a-z0-9\u00a1-\uffff](?:-)*)*(?:[a-z0-9\u00a1-\uffff])+)*(?:\\.(?:[a-z0-9\u00a1-\uffff]){2,})(?::(?:\\d){2,5})?(?:/(?:\\S)*)?$"
return(grepl(pattern,url))
}
#
# setup the routes ====
#
createAPI = function() {
AuthFilter = openeo.server$createFilter("authorization",.authorized)
ProcessGraphValidationFilter = openeo.server$createFilter("pg_validator",.validateProcessGraphFilter)
# serializer default is serializer_unboxed_json() if serializer is omitted
# server - capabilities ====
openeo.server$registerEndpoint(path = "/",
method = "GET",
handler = .capabilities)
# server - output formats ====
openeo.server$registerEndpoint(path="/output_formats",
method="GET",
handler = .output_formats)
# server - service types ====
openeo.server$registerEndpoint(path="/service_types",
method="GET",
handler=.services)
# server - udf runtimes ====
openeo.server$registerEndpoint(path="/udf_runtimes",
method="GET",
handler=.udf_runtimes)
# server - subscription ====
openeo.server$registerEndpoint(path="/subscription",
method = "GET",
unsupported = TRUE)
# server - validation ====
openeo.server$registerEndpoint(path="/validation",
method="POST",
handler=.validateProcessGraph)
# credentials - basic ====
openeo.server$registerEndpoint(path="/credentials/basic",
method="GET",
handler = .login_basic)
# credentials - oidc ====
# this is just for testing, there is no actual oidc integration for this back-end
# openeo.server$registerEndpoint(path="/credentials/oidc",
# method="GET",
# handler=.login_oidc)
openeo.server$registerEndpoint(path="/credentials/oidc",
method="GET",
unsupported = TRUE)
# collections - list all ====
openeo.server$registerEndpoint(path="/collections",
method="GET",
handler=.listData)
# collections - describe data ====
openeo.server$registerEndpoint(path="/collections/{name}",
method="GET",
handler=.describeData)
# processes - list all ====
openeo.server$registerEndpoint(path="/processes",
method="GET",
handler=.listProcesses)
# me - user information ====
openeo.server$registerEndpoint(path = "/me",
method="GET",
filters = list(AuthFilter),
handler = .userInformation)
# jobs - list all ====
openeo.server$registerEndpoint(path="/jobs",
method="GET",
handler=.listUserJobs,
filters = list(AuthFilter))
# jobs - create new ====
openeo.server$registerEndpoint(path = "/jobs",
method = "POST",
handler = .createNewJob,
filters = list(AuthFilter,
ProcessGraphValidationFilter))
# jobs - describe ====
openeo.server$registerEndpoint(path = "/jobs/{job_id}",
method = "GET",
handler = .describeJob,
filters = list(AuthFilter))
# jobs - update ====
openeo.server$registerEndpoint(path = "/jobs/{job_id}",
method = "PATCH",
handler = .updateJob,
filters = list(AuthFilter,
ProcessGraphValidationFilter))
# jobs - delete ====
openeo.server$registerEndpoint(path = "/jobs/{job_id}",
method = "DELETE",
handler = .deleteJob,
filters = list(AuthFilter))
# jobs - perform async ====
openeo.server$registerEndpoint(path = "/jobs/{job_id}/results",
method = "POST",
handler = .performJob,
filters = list(AuthFilter))
# jobs - describe job results ====
openeo.server$registerEndpoint(path = "/jobs/{job_id}/results",
method = "GET",
handler = .createDownloadableFileList,
filters = list(AuthFilter))
# jobs - cost estimation ====
openeo.server$registerEndpoint(path = "/jobs/{job_id}/estimate",
method = "GET",
handler = .estimateJobCosts,
filters = list(AuthFilter))
# files - list all ====
openeo.server$registerEndpoint(path = "/files/{user_id}",
method = "GET",
handler = .listUserFiles,
filters = list(AuthFilter))
# files - download file ====
openeo.server$registerEndpoint(path = "/files/{user_id}/{path}",
method = "GET",
handler = .downloadUserFile,
filters = list(AuthFilter))
# files - upload file ====
openeo.server$registerEndpoint(path = "/files/{user_id}/{path}",
method = "PUT",
handler = .uploadFile,
filters = list(AuthFilter)) # think about a new filter that handles the URL encoding
# files - delete file ====
openeo.server$registerEndpoint(path = "/files/{user_id}/{path}",
method = "DELETE",
handler = .deleteUserFile,
filters = list(AuthFilter))
# graphs - create ====
openeo.server$registerEndpoint(path = "/process_graphs",
method = "POST",
handler = .createProcessGraph,
filters = list(AuthFilter,
ProcessGraphValidationFilter))
# graphs - list all ====
openeo.server$registerEndpoint(path = "/process_graphs",
method = "GET",
handler = .listUserProcessGraphs,
filters = list(AuthFilter))
# graphs - delete ====
openeo.server$registerEndpoint(path = "/process_graphs/{process_graph_id}",
method = "DELETE",
handler = .deleteProcessGraph,
filters = list(AuthFilter))
# graphs - describe ====
openeo.server$registerEndpoint(path = "/process_graphs/{process_graph_id}",
method = "GET",
handler = .getProcessGraph,
filters = list(AuthFilter))
# graphs - update ====
openeo.server$registerEndpoint(path = "/process_graphs/{process_graph_id}",
method = "PATCH",
handler = .modifyProcessGraph,
filters = list(AuthFilter,
ProcessGraphValidationFilter))
# server - preview ====
openeo.server$registerEndpoint(path = "/preview",
method = "POST",
handler = .executeSynchronous,
filters = list(AuthFilter,
ProcessGraphValidationFilter))
# services - create ====
openeo.server$registerEndpoint(path = "/services",
method = "POST",
handler = .createNewService,
filters = list(AuthFilter,
ProcessGraphValidationFilter))
# services - list all ====
openeo.server$registerEndpoint(path = "/services",
method = "GET",
handler = .listUserServices,
filters = list(AuthFilter))
# services - describe ====
openeo.server$registerEndpoint(path = "/services/{service_id}",
method = "GET",
handler = .getServiceInformation,
filters = list(AuthFilter))
# services - delete ====
openeo.server$registerEndpoint(path = "/services/{service_id}",
method = "DELETE",
handler = .deleteService,
filters = list(AuthFilter))
# services - update ====
openeo.server$registerEndpoint(path = "/services/{service_id}",
method = "PATCH",
handler = .updateService,
filters = list(AuthFilter,
ProcessGraphValidationFilter))
# wms - referer ====
openeo.server$registerEndpoint(path = "/wms/{service_id}",
method = "GET",
handler = .referToMapserver,
serializer = serializer_proxy(),
unsupported = TRUE)
# wfs - referer ====
openeo.server$registerEndpoint(path = "/wfs/{service_id}",
method = "GET",
handler = .referToMapserver,
serializer = serializer_proxy(),
unsupported = TRUE)
# server - udf runtimes ====
openeo.server$registerEndpoint(path = "/udf_runtimes",
method = "GET",
unsupported = TRUE)
# server - describe udf runtime
openeo.server$registerEndpoint(path = "/udf_runtimes/{lang}/{udf_type}",
method = "GET",
unsupported = TRUE)
invisible(TRUE)
}
forward = function() {
openeo.globals$forwarded=TRUE
}
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.