Skip to content

Commit

Permalink
Release 1.1.1
Browse files Browse the repository at this point in the history
Merge pull request #425 from imotions/develop
  • Loading branch information
Whathecode authored Jan 2, 2023
2 parents 00848dd + 604638b commit cbda553
Show file tree
Hide file tree
Showing 229 changed files with 1,517 additions and 1,515 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ CARP Core is a software framework to help developers build research platforms to
It provides modules to define, deploy, and monitor research studies, and to collect data from multiple devices at multiple locations.

It is the result of a collaboration between [iMotions](https://imotions.com/) and the [Copenhagen Center for Health Technology (CACHET)](https://www.cachet.dk/).
Both use CARP Core to implement their respective research platforms: the [iMotions Mobile Research Platform](https://imotions.com/mobile-platform-landing-page-submissions/) and the [CACHET Research Platform (CARP)](https://carp.cachet.dk/).
Both use CARP Core to implement their respective research platforms: the [iMotions Mobile Research Platform](https://imotions.com/mobile-platform-landing-page-submissions/) and the [Copenhagen Research Platform (CARP)](https://carp.cachet.dk/).
CARP Core is now maintained fully by iMotions (since 1.0), but [still part of CARP](https://carp.cachet.dk/core/) as an ongoing collaboration.

Following [domain-driven design](https://en.wikipedia.org/wiki/Domain-driven_design), this project contains all domain models and application services for all CARP subsystems ([depicted below](#architecture)), not having any dependencies on concrete infrastructure.
Expand Down
24 changes: 12 additions & 12 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,28 @@ buildscript {
ext {
// Version used for submodule artifacts.
// Snapshot publishing changes (or adds) the suffix after '-' with 'SNAPSHOT' prior to publishing.
globalVersion = '1.1.0'
clientsVersion = '1.1.0-alpha.1' // The clients subsystem is still expected to change drastically.
globalVersion = '1.1.1'
clientsVersion = '1.1.1-alpha.1' // The clients subsystem is still expected to change drastically.

versions = [
// Kotlin multiplatform versions.
kotlin:'1.6.10',
serialization:'1.3.2',
coroutines:'1.6.0',
datetime:'0.3.2',
kotlin:'1.8.0',
serialization:'1.4.1',
coroutines:'1.6.4',
datetime:'0.4.0',

// JVM versions.
jvmTarget:'1.8',
dokkaPlugin:'1.6.10',
dokkaPlugin:'1.7.20',
reflections:'0.10.2',

// JS versions.
nodePlugin:'3.2.1',
bigJs:'6.1.1',
nodePlugin:'3.5.0',
bigJs:'6.2.1',

// DevOps versions.
detektPlugin:'1.20.0-RC2',
detektVerifyImplementation:'1.2.2',
detektPlugin:'1.22.0',
detektVerifyImplementation:'1.2.5',
nexusPublishPlugin:'1.1.0',
apacheCommons:'2.11.0'
]
Expand Down Expand Up @@ -258,7 +258,7 @@ task copyTestJsSources(type: Copy, dependsOn: setupTsProject) {
if (path == ".visited") return // We don't need this file.

// Remove intermediate version directory: e.g. "kotlin/1.5.10/kotlin.js"
it.path = path.replaceFirst(/\d+\.\d+.\d+\//, "")
it.path = path.replaceFirst(/\d+\.\d+.\d+(-.+)?\//, "")
}
}
into "./$typescriptFolder/node_modules"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ sealed class StudyStatus
is DeviceDeploymentStatus.NotDeployed ->
if ( deploymentInformation == null || deviceStatus is DeviceDeploymentStatus.NeedsRedeployment )
{
if ( deviceStatus.canObtainDeviceDeployment ) AwaitingDeviceDeployment( id, deployingDevices )
val readyToDeploy = deviceStatus.canObtainDeviceDeployment
if ( readyToDeploy ) AwaitingDeviceDeployment( id, deployingDevices )
else AwaitingOtherDeviceRegistrations( id, deployingDevices )
}
else
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,6 @@ ClientManager<Smartphone, SmartphoneDeviceRegistration, SmartphoneDeviceRegistra
dataCollectorFactory
)
{
override fun createDeviceRegistrationBuilder(): SmartphoneDeviceRegistrationBuilder = SmartphoneDeviceRegistrationBuilder()
override fun createDeviceRegistrationBuilder(): SmartphoneDeviceRegistrationBuilder =
SmartphoneDeviceRegistrationBuilder()
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,16 @@ import dk.cachet.carp.common.application.data.DataType
import dk.cachet.carp.common.application.devices.DeviceRegistration
import dk.cachet.carp.common.application.devices.DeviceType

typealias DataCollectorMap = MutableMap<DeviceRegistration, AnyConnectedDeviceDataCollector>


/**
* Allows subscribing to [Data] (e.g., sensors, surveys) of requested [DataType]s on a primary device and connected devices
* by using [DeviceDataCollector] instances provided by [dataCollectorFactory].
*/
class DataListener( private val dataCollectorFactory: DeviceDataCollectorFactory )
{
private val connectedDataCollectors: MutableMap<DeviceType, MutableMap<DeviceRegistration, AnyConnectedDeviceDataCollector>> = mutableMapOf()
private val connectedDataCollectors: MutableMap<DeviceType, DataCollectorMap> = mutableMapOf()


/**
Expand Down Expand Up @@ -40,7 +42,10 @@ class DataListener( private val dataCollectorFactory: DeviceDataCollectorFactory
*
* @return The [ConnectedDeviceDataCollector], or null in case it could not be created.
*/
fun tryGetConnectedDataCollector( connectedDeviceType: DeviceType, registration: DeviceRegistration ): AnyConnectedDeviceDataCollector?
fun tryGetConnectedDataCollector(
connectedDeviceType: DeviceType,
registration: DeviceRegistration
): AnyConnectedDeviceDataCollector?
{
return try
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ class Study(
fun validateDeviceDeployment( dataListener: DataListener )
{
val deployment = checkNotNull( deploymentInformation )
val remainingDevicesToRegister = deploymentStatus?.getRemainingDevicesToRegister() ?: emptySet()
val remainingDevicesToRegister = deploymentStatus?.getRemainingDevicesToRegister().orEmpty()

// All devices need to be registered before deployment can be validated.
check( remainingDevicesToRegister.isEmpty() )
Expand All @@ -131,7 +131,9 @@ class Study(
if ( device.isConnectedDevice )
{
dataListener.tryGetConnectedDataCollector( deviceType, registration )
?: throw UnsupportedOperationException( "Connecting to device of type \"$deviceType\" is not supported on this client." )
?: throw UnsupportedOperationException(
"Connecting to device of type \"$deviceType\" is not supported on this client."
)
}

val dataTypes = device.tasks
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@ class StudyDeploymentProxy(
study.deploymentStatusReceived( deployedStatus )
}
// Handle race conditions with competing clients modifying device registrations, invalidating this deployment.
catch ( ignore: IllegalArgumentException ) { } // TODO: When deployment is out of date, maybe also use `IllegalStateException` for easier handling here.
// TODO: When deployment is out of date, maybe also use `IllegalStateException` for easier handling here.
catch ( ignore: IllegalArgumentException ) { }
catch ( ignore: IllegalStateException ) { }
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import dk.cachet.carp.common.domain.Snapshot
import dk.cachet.carp.deployments.application.PrimaryDeviceDeployment
import dk.cachet.carp.deployments.application.StudyDeploymentStatus
import kotlinx.datetime.Instant
import kotlinx.serialization.Serializable
import kotlinx.serialization.*


@Serializable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@ class InMemoryClientRepository : ClientRepository
override suspend fun updateStudy( study: Study )
{
val storedStudy = findStudySnapshot( study )
requireNotNull( storedStudy ) { "The repository does not contain an existing study matching the one to update." }
requireNotNull( storedStudy )
{ "The repository does not contain an existing study matching the one to update." }

studies.remove( storedStudy )
studies.add( study.getSnapshot() )
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,8 @@ class ClientCodeSamples
* A stub [DataListener] which supports the expected data types in [createExampleProtocol].
*/
private fun createDataCollectorFactory() = createDataCollectorFactory(
CarpDataTypes.GEOLOCATION, CarpDataTypes.STEP_COUNT
CarpDataTypes.GEOLOCATION,
CarpDataTypes.STEP_COUNT
)

private val accountService = InMemoryAccountService()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,11 @@ suspend fun createStudyDeployment( protocol: StudyProtocol ): Pair<DeploymentSer
eventBus.createApplicationServiceAdapter( DeploymentService::class )
)
val invitation = createParticipantInvitation()
val status = deploymentService.createStudyDeployment( UUID.randomUUID(), protocol.getSnapshot(), listOf( invitation ) )
val status = deploymentService.createStudyDeployment(
UUID.randomUUID(),
protocol.getSnapshot(),
listOf( invitation )
)
return Pair( deploymentService, status )
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ interface ClientRepositoryTest
// Make some changes and update.
val primaryDevice = StubPrimaryDeviceConfiguration( deviceRoleName )
val registration = primaryDevice.createRegistration()
val primaryDeviceDeployment = PrimaryDeviceDeployment( StubPrimaryDeviceConfiguration( deviceRoleName ), registration )
val primaryDeviceDeployment = PrimaryDeviceDeployment( primaryDevice, registration )
study.deploymentStatusReceived(
StudyDeploymentStatus.DeployingDevices(
Clock.System.now(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,12 @@ class DataListenerTest
fun unsupported_connected_devices()
{
val factory = StubConnectedDeviceDataCollectorFactory( emptySet(), emptyMap() ) // Nothing is supported.
val listener = DataListener( factory )

val unsupportedDeviceType = StubDeviceConfiguration::class
val registration = StubDeviceConfiguration().createRegistration()
assertNull( listener.tryGetConnectedDataCollector( unsupportedDeviceType, registration ) )
assertFalse( listener.supportsDataOnConnectedDevice( STUB_DATA_POINT_TYPE, unsupportedDeviceType, registration ) )

DataListener( factory ).apply {
assertNull( tryGetConnectedDataCollector( unsupportedDeviceType, registration ) )
assertFalse( supportsDataOnConnectedDevice( STUB_DATA_POINT_TYPE, unsupportedDeviceType, registration ) )
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,6 @@ class StubDeviceDataCollectorFactory(
override fun createConnectedDataCollector(
deviceType: DeviceType,
deviceRegistration: DeviceRegistration
): AnyConnectedDeviceDataCollector = StubConnectedDeviceDataCollector( connectedSupportedDataTypes, deviceRegistration )
): AnyConnectedDeviceDataCollector =
StubConnectedDeviceDataCollector( connectedSupportedDataTypes, deviceRegistration )
}
Original file line number Diff line number Diff line change
Expand Up @@ -220,10 +220,12 @@ class StudyDeploymentProxyTest
fun tryDeployment_succeeds_when_data_types_of_protocol_measures_are_supported() = runTest {
// Create protocol that measures on smartphone and one connected device.
val protocol = createSmartphoneWithConnectedDeviceStudy()
val primaryTask = StubTaskConfiguration( "Primary measure", listOf( Measure.DataStream( STUB_DATA_POINT_TYPE ) ) )
val primaryTask =
StubTaskConfiguration( "Primary measure", listOf( Measure.DataStream( STUB_DATA_POINT_TYPE ) ) )
protocol.addTaskControl( smartphone.atStartOfStudy().start( primaryTask, smartphone ) )
val connectedDataType = DataType( "custom", "type" )
val connectedTask = StubTaskConfiguration( "Connected measure", listOf( Measure.DataStream( connectedDataType ) ) )
val connectedTask =
StubTaskConfiguration( "Connected measure", listOf( Measure.DataStream( connectedDataType ) ) )
protocol.addTaskControl( smartphone.atStartOfStudy().start( connectedTask, connectedDevice ) )

// Create a data listener which supports the requested devices and types in the protocol
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,9 @@ import dk.cachet.carp.common.infrastructure.services.ApplicationServiceRequest
import dk.cachet.carp.common.infrastructure.test.createTestJSON
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.KSerializer
import kotlinx.serialization.descriptors.elementDescriptors
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.*
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.json.*
import kotlin.test.*


Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,8 @@
package dk.cachet.carp.common.test.infrastructure.versioning

import dk.cachet.carp.common.infrastructure.services.LoggedRequest
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.JsonClassDiscriminator
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.*
import kotlinx.serialization.json.*


/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import dk.cachet.carp.common.application.services.ApplicationService
import dk.cachet.carp.common.application.services.IntegrationEvent
import dk.cachet.carp.common.domain.Snapshot
import dk.cachet.carp.common.test.infrastructure.SnapshotTest
import kotlinx.serialization.builtins.serializer
import org.reflections.Reflections
import kotlin.jvm.internal.Reflection
import kotlin.reflect.KClass
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,9 @@ import dk.cachet.carp.common.infrastructure.test.createTestJSON
import dk.cachet.carp.common.infrastructure.versioning.ApplicationServiceApiMigrator
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.builtins.ListSerializer
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.jsonPrimitive
import kotlinx.serialization.serializer
import kotlinx.serialization.*
import kotlinx.serialization.builtins.*
import kotlinx.serialization.json.*
import org.apache.commons.io.FileUtils
import java.io.File
import kotlin.reflect.KClass
Expand Down Expand Up @@ -46,7 +43,7 @@ abstract class BackwardsCompatibilityTest<TService : ApplicationService<TService
fun setup()
{
// Get available test versions.
val directories = testRequestsFolder.listFiles()?.filter { it.isDirectory } ?: emptyList()
val directories = testRequestsFolder.listFiles()?.filter { it.isDirectory }.orEmpty()
availableTestVersions = directories.map {
val versionMatch = assertNotNull(
Regex( """(\d)\.(\d)""" ).find( it.name ),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
package dk.cachet.carp.common.application

import dk.cachet.carp.common.infrastructure.serialization.createCarpStringPrimitiveSerializer
import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializable
import kotlinx.serialization.*


/**
Expand All @@ -19,4 +18,5 @@ data class EmailAddress( val address: String )
/**
* A custom serializer for [EmailAddress].
*/
object EmailAddressSerializer : KSerializer<EmailAddress> by createCarpStringPrimitiveSerializer( { EmailAddress( it ) } )
object EmailAddressSerializer : KSerializer<EmailAddress> by
createCarpStringPrimitiveSerializer( { EmailAddress( it ) } )
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
package dk.cachet.carp.common.application

import dk.cachet.carp.common.infrastructure.serialization.createCarpStringPrimitiveSerializer
import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializable
import kotlinx.serialization.*


/**
Expand All @@ -25,7 +24,10 @@ data class MACAddress(
init
{
require( MACAddressRegex.matches( this.address ) )
{ "Invalid MAC address string representation: expected six groups of two upper case hexadecimal digits, separated by hyphens (-)." }
{
"Invalid MAC address string representation: " +
"expected six groups of two upper case hexadecimal digits, separated by hyphens (-)."
}
}


Expand All @@ -42,7 +44,11 @@ data class MACAddress(
fun parse( address: String ): MACAddress
{
require( address.split( ':' ).size == GROUPS || address.split( '-' ).size == GROUPS )
{ "Invalid MAC address string representation: expected six groups of two hexadecimal digits (upper or lower case), separated by hyphens (-) or colons (:)." }
{
"Invalid MAC address string representation: " +
"expected six groups of two hexadecimal digits (upper or lower case), " +
"separated by hyphens (-) or colons (:)."
}

val recommendedFormatting = address.uppercase().replace( ':', '-' )
return MACAddress( recommendedFormatting )
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
package dk.cachet.carp.common.application

import dk.cachet.carp.common.infrastructure.serialization.createCarpStringPrimitiveSerializer
import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializable
import kotlinx.serialization.*


/**
Expand Down Expand Up @@ -31,9 +30,15 @@ data class NamespacedId(
init
{
require( namespaceRegex.matches( namespace ) )
{ "Invalid namespace representation: expected lowercase alpha-numeric (underscore included) words delimited by dots." }
{
"Invalid namespace representation: " +
"expected lowercase alpha-numeric (underscore included) words delimited by dots."
}
require( nameRegex.matches( name ) )
{ "Invalid name representation: expected a single lowercase alpha-numeric (underscore included) word." }
{
"Invalid name representation: " +
"expected a single lowercase alpha-numeric (underscore included) word."
}
}


Expand Down Expand Up @@ -78,4 +83,5 @@ val nameRegex = """^[a-z_0-9]+?$""".toRegex()
/**
* A custom serializer for [NamespacedId].
*/
object NamespacedIdSerializer : KSerializer<NamespacedId> by createCarpStringPrimitiveSerializer( { s -> NamespacedId.fromString( s ) } )
object NamespacedIdSerializer : KSerializer<NamespacedId> by
createCarpStringPrimitiveSerializer( { s -> NamespacedId.fromString( s ) } )
Loading

0 comments on commit cbda553

Please sign in to comment.