Skip to content
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ plugins {
}

group = 'org.radarbase'
version = '1.0.5'
version = '1.0.6'

application {
mainClass = 'org.radarbase.redcap.webapp.GrizzlyServer'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class EntryPointTest {
val subject: Subject? =
mpClient.getSubject(URL(REDCAP_URL), REDCAP_PROJECT_ID, REDCAP_RECORD_ID_2)
Assert.assertNotNull(subject)
Assert.assertEquals(Integer.valueOf(REDCAP_RECORD_ID_2), subject?.externalId)
Assert.assertEquals(REDCAP_RECORD_ID_2.toString(), subject?.externalId)
Assert.assertEquals(
"$WORK_PACKAGE-$MP_PROJECT_ID-$MP_PROJECT_LOCATION-$REDCAP_RECORD_ID_2",
subject?.humanReadableIdentifier
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ class IntegratorTest {

assertNotNull(subject)
assertEquals(
Integer.valueOf(REDCAP_RECORD_ID_2),
REDCAP_RECORD_ID_2.toString(),
subject?.externalId
)
assertEquals(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ import java.net.URL
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
class MpClientTest {
private var project: Project? = null
private val testAttributes: Map<String, String> =
HashMap()
private val emptyAttributes: Map<String, String> = HashMap()
private val attributes: Map<String, String> = mapOf("group" to "test")

@Before
@Throws(IOException::class, TokenException::class)
Expand All @@ -42,7 +42,7 @@ class MpClientTest {
}
mpClient.createSubject(
URL(IntegrationUtils.REDCAP_URL), project!!, IntegrationUtils.REDCAP_RECORD_ID_1,
IntegrationUtils.WORK_PACKAGE + IntegrationUtils.REDCAP_RECORD_ID_1, testAttributes
IntegrationUtils.WORK_PACKAGE + IntegrationUtils.REDCAP_RECORD_ID_1, emptyAttributes
)
val subject =
mpClient.getSubject(
Expand All @@ -51,7 +51,7 @@ class MpClientTest {
)
Assert.assertNotNull(subject)
Assert.assertEquals(
Integer.valueOf(IntegrationUtils.REDCAP_RECORD_ID_1),
IntegrationUtils.REDCAP_RECORD_ID_1.toString(),
subject!!.externalId
)
Assert.assertEquals(
Expand All @@ -69,7 +69,27 @@ class MpClientTest {
// This should throw exception since subject already exists
mpClient.createSubject(
URL(IntegrationUtils.REDCAP_URL), project!!, IntegrationUtils.REDCAP_RECORD_ID_1,
IntegrationUtils.WORK_PACKAGE + IntegrationUtils.REDCAP_RECORD_ID_1, testAttributes
IntegrationUtils.WORK_PACKAGE + IntegrationUtils.REDCAP_RECORD_ID_1, emptyAttributes
)
}

@Test
@Throws(IOException::class, URISyntaxException::class)
fun updateSubjectTest() {
if (project == null) {
projectTest()
}
val subject =
mpClient.getSubject(
URL(IntegrationUtils.REDCAP_URL), IntegrationUtils.REDCAP_PROJECT_ID,
IntegrationUtils.REDCAP_RECORD_ID_1
)
subject?.addAttributes(attributes)
val newSubject = mpClient.updateSubject(subject!!)

Assert.assertNotNull(newSubject)
Assert.assertEquals(subject, newSubject)
Assert.assertEquals(subject.attributes, newSubject.attributes)
Assert.assertEquals(subject.sources, newSubject.sources)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ object RedCapManager {
* @throws NoSuchElementException In case the [URL] does not contain any characters
*/
@Throws(MalformedURLException::class, NoSuchElementException::class)
fun getRecordUrl(redCapUrl: URL, projectId: Int, recordId: Int): URL {
fun getRecordUrl(redCapUrl: URL, projectId: Int, recordId: Int): String {
val redCapInfo = getRedCapInfo(redCapUrl, projectId)
var redCap = redCapInfo.url.toString()

Expand All @@ -107,6 +107,6 @@ object RedCapManager {

redCap = "$redCap$PROJECT_ID$projectId$RECORD_ID$recordId" +
"$EVENT_ID${redCapInfo.enrolmentEvent}$PAGE_NAME${redCapInfo.integrationForm}"
return URL(redCap)
return URL(redCap).toString()
}
}
25 changes: 12 additions & 13 deletions src/main/kotlin/org/radarbase/redcap/managementportal/MpClient.kt
Original file line number Diff line number Diff line change
Expand Up @@ -129,15 +129,16 @@ open class MpClient @Inject constructor(private val httpClient: OkHttpClient) {
val radarSubjectId = UUID.randomUUID().toString()
val subject = Subject(
subjectId = radarSubjectId,
externalId = recordId,
externalId = recordId.toString(),
externalLink = RedCapManager.getRecordUrl(
redcapUrl,
project.redCapId!!,
recordId
),
project = project,
humanReadableId = humanReadableId,
attributes = attributes
attributes = attributes,
sources = listOf()
)
val request =
getBuilder(Properties.subjectEndPoint).post(
Expand Down Expand Up @@ -182,26 +183,23 @@ open class MpClient @Inject constructor(private val httpClient: OkHttpClient) {
recordId
)
).get().build()

return performRequest(
request = request,
onSuccess = { response ->
val subjects =
Subject.subjects(
response
)
if (subjects.size == 0) {
LOGGER.info("Subject is not present")
val subjects = Subject.subjects(response)
.filter { it.externalId.toString() == recordId.toString() }
if (subjects.isEmpty()) {
LOGGER.info("Subject with externalId $recordId is not present")
return@performRequest null
}

check(subjects.size <= 1) {
("More than 1 subjects exist with same "
+ "externalId in the same Project")
"More than one subject exists with the same externalId ($recordId) in the same project"
}
subjects[0]
subjects.first()
},
onError = {
LOGGER.info("Subject is not present")
LOGGER.info("Failed to retrieve subject with externalId $recordId")
null
},
errorMessage = "Subject could not be retrieved"
Expand Down Expand Up @@ -264,6 +262,7 @@ open class MpClient @Inject constructor(private val httpClient: OkHttpClient) {
.addPathSegment(projectName)
.addPathSegment("subjects")
.addQueryParameter("externalId", recordId.toString())
.addQueryParameter("size", Int.MAX_VALUE.toString())
.build()
return subjectUrl.toUrl()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ package org.radarbase.redcap.managementportal
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.databind.DeserializationContext
import com.fasterxml.jackson.databind.JsonDeserializer
import com.fasterxml.jackson.core.JsonGenerator
import com.fasterxml.jackson.databind.JsonSerializer
import com.fasterxml.jackson.databind.SerializerProvider
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.annotation.JsonDeserialize
Expand Down Expand Up @@ -33,4 +36,17 @@ class OrganizationDeserializer : JsonDeserializer<Organization>() {
}
}
}
}

class OrganizationSerializer : JsonSerializer<Organization>() {
override fun serialize(value: Organization, gen: JsonGenerator, serializers: SerializerProvider) {
when (value) {
is Organization.StringOrganization -> {
gen.writeString(value.value)
}
is Organization.ObjectOrganization -> {
gen.writeObject(value.value)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package org.radarbase.redcap.managementportal
import com.fasterxml.jackson.annotation.JsonIgnore
import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.databind.annotation.JsonDeserialize
import com.fasterxml.jackson.databind.annotation.JsonSerialize
import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.KotlinModule
Expand Down Expand Up @@ -32,7 +33,12 @@ import org.radarbase.redcap.managementportal.Organization
data class Project(
@JsonProperty("id") val id: Int,
@JsonProperty("projectName") val projectName: String,
@JsonProperty("organization") @JsonDeserialize(using = OrganizationDeserializer::class) val organization: Organization,

@JsonProperty("organization")
@JsonDeserialize(using = OrganizationDeserializer::class)
@JsonSerialize(using = OrganizationSerializer::class)
val organization: Organization,

@JsonProperty("location") val location: String = "",
@JsonProperty("attributes") val attributes: Map<String, String> = emptyMap()
) {
Expand Down
41 changes: 26 additions & 15 deletions src/main/kotlin/org/radarbase/redcap/managementportal/Subject.kt
Original file line number Diff line number Diff line change
Expand Up @@ -31,19 +31,20 @@ import java.net.URL
/**
* Constructor.
* @param subjectId [String] representing Management Portal Subject identifier
* @param externalId [Integer] representing the REDCap Record identifier
* @param externalLink [URL] pointing the REDCap integration form / instrument
* @param externalId [String] representing the REDCap Record identifier
* @param externalLink [String] pointing the REDCap integration form / instrument
* @param project [Project] associated with the subject
* @param attributes [Map] of key,value pairs
*/
data class Subject(
@JsonProperty("id") val mpId: Long?,
@JsonProperty("login") val subjectId: String,
@JsonProperty("externalId") val externalId: Int? = null,
@JsonProperty("externalLink") val externalLink: URL? = null,
@JsonProperty("externalId") val externalId: String? = null,
@JsonProperty("externalLink") val externalLink: String? = null,
@JsonProperty("project") var project: Project? = null,
@JsonProperty("attributes") val attributes: MutableMap<String, String> = mutableMapOf(),
@JsonProperty("status") val status: String = SubjectStatus.ACTIVATED.toString()
@JsonProperty("status") val status: String = SubjectStatus.ACTIVATED.toString(),
@JsonProperty("sources") val sources: List<JsonNode>
) {
enum class SubjectStatus {
DEACTIVATED, ACTIVATED, DISCONTINUED, INVALID
Expand All @@ -57,32 +58,42 @@ data class Subject(
var operationStatus: SubjectOperationStatus =
SubjectOperationStatus.NOOP


/**
* Constructor.
* @param subjectId [String] representing Management Portal Subject identifier
* @param externalId [Integer] representing the REDCap Record identifier
* @param externalLink [URL] pointing the REDCap integration form / instrument
* @param [URL] pointing the REDCap integration form / instrument
* @param project [Project] associated with the subject
* @param humanReadableId [String] representing the value associated with
* [.HUMAN_READABLE_IDENTIFIER_KEY]
*/
constructor(
subjectId: String, externalId: Int, externalLink: URL, project: Project,
humanReadableId: String
subjectId: String,
externalId: String,
externalLink: String,
project: Project,
humanReadableId: String,
sources: List<JsonNode>
) : this(
null, subjectId, externalId, externalLink, project,
mutableMapOf<String, String>(Pair(HUMAN_READABLE_IDENTIFIER_KEY, humanReadableId))
null,
subjectId,
externalId,
externalLink,
project,
mutableMapOf(Pair(HUMAN_READABLE_IDENTIFIER_KEY, humanReadableId)),
SubjectStatus.ACTIVATED.toString(),
sources
)

constructor(
subjectId: String,
externalId: Int,
externalLink: URL,
externalId: String,
externalLink: String,
project: Project,
humanReadableId: String,
attributes: Map<String, String>
) : this(subjectId, externalId, externalLink, project, humanReadableId) {
attributes: Map<String, String>,
sources: List<JsonNode>
) : this(subjectId, externalId, externalLink, project, humanReadableId, sources) {
addAttributes(attributes)
}

Expand Down