Skip to content

Commit fa02ac1

Browse files
committed
- supports the new species list - migrated code to support both new and old species list - refactored code from biocllect to plugin
1 parent 4d9943a commit fa02ac1

File tree

5 files changed

+1178
-19
lines changed

5 files changed

+1178
-19
lines changed

build.gradle

+2
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@ dependencies {
7878
implementation "org.apache.httpcomponents:httpcore:4.4.13"
7979
implementation 'org.apache.httpcomponents:httpclient:4.5.13'
8080
implementation "org.apache.httpcomponents:httpmime:4.2.1"
81+
implementation 'com.opencsv:opencsv:5.7.0'
82+
implementation("com.squareup.okhttp3:okhttp:4.12.0")
8183

8284
console "org.grails:grails-console"
8385
profile "org.grails.profiles:web-plugin"

grails-app/conf/application.yml

+7-1
Original file line numberDiff line numberDiff line change
@@ -111,4 +111,10 @@ environments:
111111
port: "8087"
112112
spring:
113113
autoconfigure:
114-
exclude: "au.org.ala.ws.security.AlaWsSecurityConfiguration"
114+
exclude: "au.org.ala.ws.security.AlaWsSecurityConfiguration"
115+
116+
---
117+
listsFieldMappingV2:
118+
matchedName: "classification.scientificName"
119+
commonName: "classification.vernacularName"
120+
rawScientificName: "scientificName"

grails-app/services/au/org/ala/ecodata/forms/EcpWebService.groovy

+101-9
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ package au.org.ala.ecodata.forms
1717

1818

1919
import au.org.ala.ws.tokens.TokenService
20+
import com.fasterxml.jackson.databind.ObjectMapper
2021
import grails.converters.JSON
2122
import grails.core.GrailsApplication
2223
import grails.web.http.HttpHeaders
@@ -32,6 +33,8 @@ import org.apache.http.entity.mime.content.StringBody
3233
import org.grails.web.converters.exceptions.ConverterException
3334
import org.springframework.http.MediaType
3435
import org.springframework.web.multipart.MultipartFile
36+
import okhttp3.*
37+
import java.util.concurrent.TimeUnit
3538

3639
import javax.annotation.PostConstruct
3740
import javax.servlet.http.Cookie
@@ -56,13 +59,19 @@ class EcpWebService {
5659
static String AUTHORIZATION_HEADER_TYPE_EXTERNAL_TOKEN = 'externalToken'
5760

5861
static String AUTHORIZATION_HEADER_TYPE_NONE = 'none'
62+
OkHttpClient client
5963
List WHITE_LISTED_DOMAINS = []
6064

6165
TokenService tokenService
6266
@PostConstruct
6367
void init() {
6468
String whiteListed = grailsApplication.config.getProperty('app.domain.whiteList', "")
6569
WHITE_LISTED_DOMAINS = Arrays.asList(whiteListed.split(','))
70+
client = new OkHttpClient.Builder()
71+
.connectTimeout(connectTimeout(), TimeUnit.MILLISECONDS)
72+
.readTimeout(defaultTimeout(), TimeUnit.MILLISECONDS)
73+
.writeTimeout(defaultTimeout(), TimeUnit.MILLISECONDS)
74+
.build()
6675
}
6776

6877
// Used to avoid a circular dependency during initialisation
@@ -382,7 +391,7 @@ class EcpWebService {
382391
return urlConnection.content.getText(getCharset(urlConnection))
383392
}
384393

385-
def doPostWithParams(String url, Map params) {
394+
def doPostWithParams(String url, Map params, boolean userToken = false) {
386395
def conn = null
387396
def charEncoding = 'utf-8'
388397
try {
@@ -399,7 +408,7 @@ class EcpWebService {
399408
conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded")
400409
if(canAddSecret(url)) {
401410
if (useJWT())
402-
addTokenHeader(conn)
411+
addTokenHeader(conn, userToken)
403412
else
404413
conn.setRequestProperty("Authorization", grailsApplication.config.getProperty('api_key'))
405414
}
@@ -414,13 +423,13 @@ class EcpWebService {
414423
wr.flush()
415424
def resp = conn.inputStream.text
416425
wr.close()
417-
return [resp: JSON.parse(resp?:"{}")] // fail over to empty json object if empty response string otherwise JSON.parse fails
426+
return [resp: JSON.parse(resp?:"{}"), statusCode: conn.responseCode] // fail over to empty json object if empty response string otherwise JSON.parse fails
418427
} catch (SocketTimeoutException e) {
419-
def error = [error: "Timed out calling web service. URL= ${url}."]
428+
def error = [error: "Timed out calling web service. URL= ${url}.", statusCode: conn.responseCode]
420429
log.error(error as String, e)
421430
return error
422431
} catch (SocketException se) {
423-
def error = [error: "Socket connection closed. ${se.getMessage()} URL= ${url}."]
432+
def error = [error: "Socket connection closed. ${se.getMessage()} URL= ${url}.", statusCode: conn.responseCode]
424433
log.error(error as String, se)
425434
return error
426435
} catch (Exception e) {
@@ -433,7 +442,7 @@ class EcpWebService {
433442
}
434443
}
435444

436-
def doPost(String url, Map postBody, boolean useToken = false) {
445+
def doPost(String url, Map postBody, boolean useToken = false, boolean userToken = false) {
437446
useToken = useToken || useJWT()
438447
def conn = null
439448
def charEncoding = 'utf-8'
@@ -443,7 +452,7 @@ class EcpWebService {
443452
conn.setRequestProperty("Content-Type", "application/json;charset=${charEncoding}");
444453
if (canAddSecret(url)) {
445454
if (useToken) {
446-
addTokenHeader(conn)
455+
addTokenHeader(conn, userToken)
447456
} else {
448457
conn.setRequestProperty("Authorization", grailsApplication.config.getProperty('api_key'));
449458
}
@@ -511,6 +520,87 @@ class EcpWebService {
511520
}
512521
}
513522

523+
/**
524+
* Uses OkHttp3 to perform a POST request with multipart form data if file provided.
525+
* @param url
526+
* @param params
527+
* @param file
528+
* @param contentType
529+
* @param originalFilename
530+
* @param fileParamName
531+
* @param useToken
532+
* @param userToken
533+
* @return
534+
*/
535+
Map postMultipartWithOkhttp3(String url, Map params, File file,
536+
String contentType, String originalFilename,
537+
String fileParamName = 'files',
538+
boolean useToken = false,
539+
boolean userToken = false) {
540+
useToken = useToken || useJWT()
541+
Map result = [:]
542+
def user = userService.getUser()
543+
String userIdHeader = grailsApplication.config.getProperty('app.http.header.userId')
544+
545+
MultipartBody.Builder multipartBodyBuilder = new MultipartBody.Builder()
546+
.setType(MultipartBody.FORM)
547+
548+
// Add file part
549+
if (file) {
550+
RequestBody fileBody = RequestBody.create(okhttp3.MediaType.parse(contentType), file)
551+
multipartBodyBuilder.addFormDataPart(fileParamName, originalFilename ?: fileParamName, fileBody)
552+
}
553+
554+
// Add form fields
555+
params.each { key, value ->
556+
if (value) multipartBodyBuilder.addFormDataPart(key, value.toString())
557+
}
558+
559+
// Build request
560+
Request.Builder requestBuilder = new Request.Builder()
561+
.url(url)
562+
.post(multipartBodyBuilder.build())
563+
564+
565+
// Add Authorization headers
566+
if (canAddSecret(url)) {
567+
if (useToken) {
568+
if (useJWT()) {
569+
requestBuilder.addHeader("Authorization", getToken(userToken))
570+
} else {
571+
requestBuilder.addHeader("apiKey", grailsApplication.config.getProperty('api_key'))
572+
}
573+
} else {
574+
requestBuilder.addHeader("Authorization", grailsApplication.config.getProperty('api_key'))
575+
}
576+
}
577+
578+
// Add user ID if available
579+
if (user) {
580+
requestBuilder.addHeader(userIdHeader, user.userId)
581+
} else {
582+
log.warn("No user associated with request: ${url}")
583+
}
584+
585+
// Execute request
586+
try (Response response = client.newCall(requestBuilder.build()).execute()) {
587+
String responseBody = response.body().string()
588+
ObjectMapper objectMapper = new ObjectMapper()
589+
Object responseData = objectMapper.readValue(responseBody, Object)
590+
result.status = response.code()
591+
result.statusCode = response.code()
592+
result.content = responseData
593+
result.resp = responseData
594+
} catch (Exception e) {
595+
result.status = 500
596+
result.statusCode = 500
597+
result.error = "Error POSTing to ${url}"
598+
result.details = e.message
599+
}
600+
601+
return result
602+
}
603+
514604
/**
515605
* Forwards a HTTP multipart/form-data request to ecodata.
516606
* @param url the URL to forward to.
@@ -532,9 +622,11 @@ class EcpWebService {
532622
* @param originalFilename the original file name of the data to be posted
533623
* @param fileParamName the name of the HTTP parameter that will be used for the post.
534624
* @param successHandler optional callback for a successful service invocation. If not supplied, a Map will be returned.
625+
* @param useToken whether to use the JWT token for the request.
626+
* @param userToken whether to use the user token or system token for the request.
535627
* @return [status:<request status>, content:<The response content from the server, assumed to be JSON>
536628
*/
537-
def postMultipart(url, Map params, InputStream contentIn, contentType, originalFilename, fileParamName = 'files', Closure successHandler = null, boolean useToken = false) {
629+
def postMultipart(url, Map params, InputStream contentIn, contentType, originalFilename, fileParamName = 'files', Closure successHandler = null, boolean useToken = false, boolean userToken = false) {
538630
useToken = useToken || useJWT()
539631
def result = [:]
540632
def user = userService.getUser()
@@ -552,7 +644,7 @@ class EcpWebService {
552644
if (canAddSecret(url)) {
553645
if (useToken) {
554646
if (useJWT()) {
555-
headers.'Authorization' = getToken()
647+
headers.'Authorization' = getToken(userToken)
556648
}
557649
else {
558650
headers.'apiKey' = grailsApplication.config.getProperty('api_key')

0 commit comments

Comments
 (0)