blob: 61e0614652b7af89b0905c562ca2773229479a0c [file] [log] [blame]
Marc Kupietzfd9e7492019-11-08 15:45:18 +01001################################################################################
2# Use setClassUnion to define the unholy NULL-data union as a virtual class.
3################################################################################
4setClassUnion("characterOrNULL", c("character", "NULL"))
Marc Kupietza4675722022-02-23 23:55:15 +01005setClassUnion("listOrNULL", c("list", "NULL"))
Marc Kupietzfd9e7492019-11-08 15:45:18 +01006
Marc Kupietze95108e2019-09-18 13:23:58 +02007#' Class KorAPConnection
Marc Kupietz25aebc32019-09-16 18:40:50 +02008#'
Marc Kupietz67edcb52021-09-20 21:54:24 +02009#' `KorAPConnection` objects represent the connection to a KorAP server.
10#' New `KorAPConnection` objects can be created by `new("KorAPConnection")`.
Marc Kupietze95108e2019-09-18 13:23:58 +020011#'
Marc Kupietz0a96b282019-10-01 11:05:31 +020012#' @import R.cache
Marc Kupietze95108e2019-09-18 13:23:58 +020013#' @import utils
14#' @import methods
Marc Kupietze95108e2019-09-18 13:23:58 +020015#' @export
Marc Kupietza4675722022-02-23 23:55:15 +010016KorAPConnection <- setClass("KorAPConnection", slots=c(KorAPUrl="character", apiVersion="character", indexRevision="characterOrNULL", apiUrl="character", accessToken="characterOrNULL", userAgent="character", timeout="numeric", verbose="logical", cache="logical", welcome="listOrNULL"))
Marc Kupietze95108e2019-09-18 13:23:58 +020017
18#' @param .Object KorAPConnection object
19#' @param KorAPUrl the URL of the KorAP server instance you want to access.
20#' @param apiVersion which version of KorAP's API you want to connect to.
21#' @param apiUrl URL of the KorAP web service.
Marc Kupietzb956b812019-11-25 17:53:13 +010022#' @param accessToken OAuth2 access token. To use authorization based on an access token
Marc Kupietz43a6ade2020-02-18 17:01:44 +010023#' in subsequent queries, initialize your KorAP connection with
Marc Kupietz67edcb52021-09-20 21:54:24 +020024#' `kco <- new("KorAPConnection", accessToken="<access token>")`.
Marc Kupietz4862b862019-11-07 10:13:53 +010025#' In order to make the API
Marc Kupietz67edcb52021-09-20 21:54:24 +020026#' token persistent for the currently used `KorAPUrl` (you can have one
Marc Kupietz4862b862019-11-07 10:13:53 +010027#' token per KorAPUrl / KorAP server instance), use
Marc Kupietz67edcb52021-09-20 21:54:24 +020028#' `persistAccessToken(kco)`. This will store it in your keyring using the
29#' [keyring()] package. Subsequent new("KorAPConnection") calls will
Marc Kupietz4862b862019-11-07 10:13:53 +010030#' then automatically retrieve the token from your keying. To stop using a
Marc Kupietz67edcb52021-09-20 21:54:24 +020031#' persisted token, call `clearAccessToken(kco)`. Please note that for
Marc Kupietz4862b862019-11-07 10:13:53 +010032#' DeReKo, authorized queries will behave differently inside and outside the
33#' IDS, because of the special license situation. This concerns also cached
34#' results which do not take into account from where a request was issued. If
Marc Kupietz67edcb52021-09-20 21:54:24 +020035#' you experience problems or unexpected results, please try `kco <-
36#' new("KorAPConnection", cache=FALSE)` or use
37#' [clearCache()] to clear the cache completely.
Marc Kupietzd0d3e9b2019-09-24 17:36:03 +020038#' @param userAgent user agent string.
39#' @param timeout time out in seconds.
Marc Kupietz4862b862019-11-07 10:13:53 +010040#' @param verbose logical. Decides whether following operations will default to
41#' be verbose.
42#' @param cache logical. Decides if API calls are cached locally. You can clear
Marc Kupietz67edcb52021-09-20 21:54:24 +020043#' the cache with [clearCache()].
44#' @return [KorAPConnection()] object that can be used e.g. with
45#' [corpusQuery()]
Marc Kupietze95108e2019-09-18 13:23:58 +020046#'
47#' @examples
Marc Kupietz6ae76052021-09-21 10:34:00 +020048#' \dontrun{
49#'
Marc Kupietz5a519822019-09-20 21:43:52 +020050#' kcon <- new("KorAPConnection", verbose = TRUE)
Marc Kupietze95108e2019-09-18 13:23:58 +020051#' kq <- corpusQuery(kcon, "Ameisenplage")
Marc Kupietz5a519822019-09-20 21:43:52 +020052#' kq <- fetchAll(kq)
Marc Kupietz05b22772020-02-18 21:58:42 +010053#' }
Marc Kupietz7915dc42019-09-12 17:44:58 +020054#'
Marc Kupietz4862b862019-11-07 10:13:53 +010055#' \dontrun{
Marc Kupietz6ae76052021-09-21 10:34:00 +020056#'
Marc Kupietzb956b812019-11-25 17:53:13 +010057#' kcon <- new("KorAPConnection", verbose = TRUE, accessToken="e739u6eOzkwADQPdVChxFg")
Marc Kupietz4862b862019-11-07 10:13:53 +010058#' kq <- corpusQuery(kcon, "Ameisenplage", metadataOnly=FALSE)
59#' kq <- fetchAll(kq)
60#' kq@collectedMatches$snippet
61#' }
Marc Kupietz7915dc42019-09-12 17:44:58 +020062#'
Marc Kupietze95108e2019-09-18 13:23:58 +020063#' @rdname KorAPConnection-class
Marc Kupietz632cbd42019-09-06 16:04:51 +020064#' @export
Marc Kupietze95108e2019-09-18 13:23:58 +020065setMethod("initialize", "KorAPConnection",
Marc Kupietz6a3185b2021-12-07 10:23:16 +010066 function(.Object, KorAPUrl = "https://korap.ids-mannheim.de/", apiVersion = 'v1.0', apiUrl, accessToken = getAccessToken(KorAPUrl), userAgent = "R-KorAP-Client", timeout=240, verbose = FALSE, cache = TRUE) {
Marc Kupietze95108e2019-09-18 13:23:58 +020067 .Object <- callNextMethod()
68 m <- regexpr("https?://[^?]+", KorAPUrl, perl = TRUE)
69 .Object@KorAPUrl <- regmatches(KorAPUrl, m)
70 if (!endsWith(.Object@KorAPUrl, '/')) {
71 .Object@KorAPUrl <- paste0(.Object@KorAPUrl, "/")
72 }
73 if (missing(apiUrl)) {
74 .Object@apiUrl = paste0(.Object@KorAPUrl, 'api/', apiVersion, '/')
75 } else {
76 .Object@apiUrl = apiUrl
77 }
Marc Kupietzb956b812019-11-25 17:53:13 +010078 .Object@accessToken = accessToken
Marc Kupietze95108e2019-09-18 13:23:58 +020079 .Object@apiVersion = apiVersion
Marc Kupietzd0d3e9b2019-09-24 17:36:03 +020080 .Object@userAgent = userAgent
81 .Object@timeout = timeout
Marc Kupietz5a519822019-09-20 21:43:52 +020082 .Object@verbose = verbose
Marc Kupietz0a96b282019-10-01 11:05:31 +020083 .Object@cache = cache
Marc Kupietza4675722022-02-23 23:55:15 +010084 .Object@welcome = apiCall(.Object, .Object@apiUrl, json = FALSE, cache = FALSE, getHeaders = TRUE)
85 if (!is.null(.Object@welcome)) {
86 message(.Object@welcome[[2]])
87 }
88 .Object@indexRevision <- .Object@welcome[[1]][["x-index-revision"]]
Marc Kupietze95108e2019-09-18 13:23:58 +020089 .Object
90 })
91
Marc Kupietza96537f2019-11-09 23:07:44 +010092
Marc Kupietzb956b812019-11-25 17:53:13 +010093accessTokenServiceName <- "RKorAPClientAccessToken"
Marc Kupietz4862b862019-11-07 10:13:53 +010094
Marc Kupietzb956b812019-11-25 17:53:13 +010095setGeneric("persistAccessToken", function(kco, ...) standardGeneric("persistAccessToken") )
Marc Kupietz4862b862019-11-07 10:13:53 +010096
Marc Kupietzb956b812019-11-25 17:53:13 +010097#' @aliases persistAccessToken
Marc Kupietz4862b862019-11-07 10:13:53 +010098#' @rdname KorAPConnection-class
99#' @import keyring
100#' @export
101#' @examples
102#' \dontrun{
Marc Kupietz6ae76052021-09-21 10:34:00 +0200103#'
Marc Kupietzb956b812019-11-25 17:53:13 +0100104#' kco <- new("KorAPConnection", accessToken="e739u6eOzkwADQPdVChxFg")
105#' persistAccessToken(kco)
Marc Kupietz4862b862019-11-07 10:13:53 +0100106#' }
107#'
Marc Kupietzb956b812019-11-25 17:53:13 +0100108setMethod("persistAccessToken", "KorAPConnection", function(kco, accessToken = kco@accessToken) {
109 if (is.null(accessToken))
110 stop("It seems that you have not supplied any access token that could be persisted.", call. = FALSE)
Marc Kupietz4862b862019-11-07 10:13:53 +0100111
Marc Kupietzb956b812019-11-25 17:53:13 +0100112 kco@accessToken <- accessToken
113 key_set_with_value(accessTokenServiceName, kco@KorAPUrl, accessToken)
Marc Kupietz4862b862019-11-07 10:13:53 +0100114})
115
Marc Kupietzb956b812019-11-25 17:53:13 +0100116setGeneric("clearAccessToken", function(kco) standardGeneric("clearAccessToken") )
Marc Kupietz4862b862019-11-07 10:13:53 +0100117
Marc Kupietzb956b812019-11-25 17:53:13 +0100118#' @aliases clearAccessToken
Marc Kupietz4862b862019-11-07 10:13:53 +0100119#' @rdname KorAPConnection-class
120#' @import keyring
121#' @export
122#' @examples
123#' \dontrun{
Marc Kupietz6ae76052021-09-21 10:34:00 +0200124#'
Marc Kupietz4862b862019-11-07 10:13:53 +0100125#' kco <- new("KorAPConnection")
Marc Kupietzb956b812019-11-25 17:53:13 +0100126#' clearAccessToken(kco)
Marc Kupietz4862b862019-11-07 10:13:53 +0100127#' }
128#'
Marc Kupietzb956b812019-11-25 17:53:13 +0100129setMethod("clearAccessToken", "KorAPConnection", function(kco) {
130 key_delete(accessTokenServiceName, kco@KorAPUrl)
Marc Kupietz4862b862019-11-07 10:13:53 +0100131})
132
133#' @import keyring
Marc Kupietzb956b812019-11-25 17:53:13 +0100134getAccessToken <- function(KorAPUrl) {
Marc Kupietz59e449b2019-12-12 12:53:54 +0100135 keyList <- tryCatch(withCallingHandlers(key_list(service = accessTokenServiceName),
Marc Kupietzddce5562019-11-24 15:45:38 +0100136 warning = function(w) invokeRestart("muffleWarning"),
Marc Kupietz59e449b2019-12-12 12:53:54 +0100137 error = function(e) return(NULL)),
138 error = function(e) { })
Marc Kupietz01c24772021-07-14 18:27:36 +0200139 if (KorAPUrl %in% keyList$username)
Marc Kupietzb956b812019-11-25 17:53:13 +0100140 key_get(accessTokenServiceName, KorAPUrl)
Marc Kupietzfd9e7492019-11-08 15:45:18 +0100141 else
142 NULL
Marc Kupietz4862b862019-11-07 10:13:53 +0100143}
Marc Kupietz0a96b282019-10-01 11:05:31 +0200144
Marc Kupietz581a29b2021-09-04 20:51:04 +0200145
146warnIfNoAccessToken <- function(kco) {
147 if (is.null(kco@accessToken)) {
148 warning(
149 paste0(
150 "In order to receive KWICSs also from corpora with restricted licenses, you need an access token.\n",
151 "To generate an access token, login to KorAP and navigite to KorAP's OAuth settings <",
152 kco@KorAPUrl,
153 "settings/oauth#page-top>"
154 )
155 )
156 }
157}
158
Marc Kupietz0a96b282019-10-01 11:05:31 +0200159KorAPCacheSubDir <- function() {
Marc Kupietz70b2c722020-02-18 13:32:09 +0100160 paste0("RKorAPClient_",
161 gsub(
162 "^([0-9]+\\.[0-9]+).*",
163 "\\1",
164 packageVersion("RKorAPClient"),
165 perl = TRUE
166 ))
Marc Kupietz0a96b282019-10-01 11:05:31 +0200167}
168
Marc Kupietzd0d3e9b2019-09-24 17:36:03 +0200169setGeneric("apiCall", function(kco, ...) standardGeneric("apiCall") )
170
Marc Kupietz4de53ec2019-10-04 09:12:00 +0200171## quiets concerns of R CMD check re: the .'s that appear in pipelines
172if(getRversion() >= "2.15.1") utils::globalVariables(c("."))
173
Marc Kupietzd0d3e9b2019-09-24 17:36:03 +0200174#' @aliases apiCall
175#' @rdname KorAPConnection-class
176#' @param kco KorAPConnection object
177#' @param url request url
Marc Kupietzb2b32a32020-03-24 13:56:50 +0100178#' @param json logical that determines if json result is expected
Marc Kupietzb49afa02020-06-04 15:50:29 +0200179#' @param getHeaders logical that determines if headers and content should be returned (as a list)
Marc Kupietz69cc54a2019-09-30 12:06:54 +0200180#' @importFrom jsonlite fromJSON
Marc Kupietza4675722022-02-23 23:55:15 +0100181#' @importFrom curl has_internet
Marc Kupietz69cc54a2019-09-30 12:06:54 +0200182#' @export
Marc Kupietza4675722022-02-23 23:55:15 +0100183setMethod("apiCall", "KorAPConnection", function(kco, url, json = TRUE, getHeaders = FALSE, cache = kco@cache, timeout=kco@timeout) {
Marc Kupietzb2b32a32020-03-24 13:56:50 +0100184 result <- ""
185 if (cache) {
Marc Kupietzb49afa02020-06-04 15:50:29 +0200186 result <- R.cache::loadCache(dir=KorAPCacheSubDir(), key=list(url, kco@accessToken, kco@indexRevision))
Marc Kupietzb2b32a32020-03-24 13:56:50 +0100187 if (!is.null(result)) {
188 if (!is.null(result$meta))
189 result$meta$cached <- "local"
190 return(result)
Marc Kupietz0a96b282019-10-01 11:05:31 +0200191 }
192 }
Marc Kupietza4675722022-02-23 23:55:15 +0100193
194 # From https://community.rstudio.com/t/internet-resources-should-fail-gracefully/49199/11
195 # Thanks to kvasilopoulos
196 try_GET <- function(x, ...) {
197 tryCatch(
198 GET(url = x, timeout(timeout), ...),
199 error = function(e) conditionMessage(e),
200 warning = function(w) conditionMessage(w)
201 )
202 }
203 is_response <- function(x) {
204 class(x) == "response"
205 }
206
207 # First check internet connection
208 if (!curl::has_internet()) {
209 message("No internet connection.")
210 return(invisible(NULL))
211 }
212
Marc Kupietzb956b812019-11-25 17:53:13 +0100213 if (!is.null(kco@accessToken))
Marc Kupietza4675722022-02-23 23:55:15 +0100214 resp <- try_GET(url, user_agent(kco@userAgent), timeout(timeout), add_headers(Authorization = paste("Bearer", kco@accessToken)))
Marc Kupietz4862b862019-11-07 10:13:53 +0100215 else
Marc Kupietza4675722022-02-23 23:55:15 +0100216 resp <- try_GET(url, user_agent(kco@userAgent), timeout(timeout))
217
218 if (!is_response(resp)) {
219 message(resp)
220 return(invisible(NULL))
221 }
Marc Kupietzb2b32a32020-03-24 13:56:50 +0100222 if (json || status_code(resp) != 200) {
223 if (json && !http_type(resp) %in% c("application/json", "application/ld+json")) {
Marc Kupietza4675722022-02-23 23:55:15 +0100224 # message("API did not return json")
225 return(invisible(NULL))
Marc Kupietzb2b32a32020-03-24 13:56:50 +0100226 }
227 result <- jsonlite::fromJSON(content(resp, "text", encoding = "UTF-8"))
228 if (!is.null(result$warnings)) {
Marc Kupietza4675722022-02-23 23:55:15 +0100229 msg <- if (nrow(result$warnings) > 1)
Marc Kupietzb2b32a32020-03-24 13:56:50 +0100230 sapply(result$warnings, function(warning) paste(sprintf("%s: %s", warning[1], warning[2]), sep="\n"))
231 else
232 sprintf("%s: %s", result$warnings[1], result$warnings[2])
Marc Kupietza4675722022-02-23 23:55:15 +0100233 message(msg)
Marc Kupietzb2b32a32020-03-24 13:56:50 +0100234 }
Marc Kupietzd0d3e9b2019-09-24 17:36:03 +0200235 }
236 if (status_code(resp) != 200) {
Marc Kupietzb7d8c272020-01-31 18:51:50 +0100237 if (kco@verbose) {
238 cat("\n")
239 }
Marc Kupietza4675722022-02-23 23:55:15 +0100240 msg <- sprintf("%s KorAP API request failed", status_code(resp))
Marc Kupietzb2b32a32020-03-24 13:56:50 +0100241 if (!is.null(result$errors)) {
Marc Kupietza4675722022-02-23 23:55:15 +0100242 errormsg <- unlist(result$errors)
243 msg <- sprintf("%s: %s %s", msg, errormsg[5], errormsg[2])
Marc Kupietzb7d8c272020-01-31 18:51:50 +0100244 }
Marc Kupietza4675722022-02-23 23:55:15 +0100245 message(msg)
246 return(invisible(NULL))
Marc Kupietzd0d3e9b2019-09-24 17:36:03 +0200247 }
Marc Kupietzb2b32a32020-03-24 13:56:50 +0100248 if (!json) {
249 result <- content(resp, "text", encoding = "UTF-8")
Marc Kupietz0a96b282019-10-01 11:05:31 +0200250 }
Marc Kupietzb2b32a32020-03-24 13:56:50 +0100251 if (cache) {
Marc Kupietzb49afa02020-06-04 15:50:29 +0200252 R.cache::saveCache(result, key = list(url, kco@accessToken, kco@indexRevision), dir = KorAPCacheSubDir(), compress = TRUE)
Marc Kupietzb2b32a32020-03-24 13:56:50 +0100253 }
Marc Kupietzb49afa02020-06-04 15:50:29 +0200254 if (getHeaders) {
255 list(httr::headers(resp), result)
256 } else {
257 result
258 }
Marc Kupietzd0d3e9b2019-09-24 17:36:03 +0200259})
260
Marc Kupietz0a96b282019-10-01 11:05:31 +0200261setGeneric("clearCache", function(kco) standardGeneric("clearCache") )
262
263#' @aliases clearCache
264#' @rdname KorAPConnection-class
265#' @export
266setMethod("clearCache", "KorAPConnection", function(kco) {
267 R.cache::clearCache(dir=KorAPCacheSubDir())
268})
269
Marc Kupietze95108e2019-09-18 13:23:58 +0200270#' @rdname KorAPConnection-class
271#' @param object KorAPConnection object
272#' @export
273setMethod("show", "KorAPConnection", function(object) {
274 cat("<KorAPConnection>", "\n")
275 cat("apiUrl: ", object@apiUrl, "\n")
276})
277
Marc Kupietzd0d3e9b2019-09-24 17:36:03 +0200278##' Funtion KorAPConnection()
279##'
280##' Wrappper function for new("KorAPConnection")
281##'
282##' @rdname KorAPConnection-constructor
283##' @name KorAPConnection-constructor
284##' @export
285## XKorAPConnection <- function(...) new("KorAPConnection", ...)