diff --git a/libs/deserializer/src/main/java/de/cyface/deserializer/BinaryFormatDeserializer.java b/libs/deserializer/src/main/java/de/cyface/deserializer/BinaryFormatDeserializer.java index 016ae5f..f2bd833 100644 --- a/libs/deserializer/src/main/java/de/cyface/deserializer/BinaryFormatDeserializer.java +++ b/libs/deserializer/src/main/java/de/cyface/deserializer/BinaryFormatDeserializer.java @@ -29,7 +29,7 @@ import de.cyface.deserializer.exceptions.InvalidLifecycleEvents; import de.cyface.model.Measurement; import de.cyface.model.MeasurementIdentifier; -import de.cyface.model.MetaData; +import de.cyface.model.RequestMetaData; /** * A {@link Deserializer} for a file in Cyface binary format. Constructs a new measurement from a ZLIB compressed @@ -62,7 +62,7 @@ public class BinaryFormatDeserializer implements Deserializer { * The meta information about the {@link Measurement}. This information is not part of the datafiles but is usually * stored alongside the binary data. It is usually used to get a glimpse into what data to expect. */ - private final MetaData metaData; + private final RequestMetaData metaData; /** * The stream of compressed data to load locations and measured data points from */ @@ -77,7 +77,7 @@ public class BinaryFormatDeserializer implements Deserializer { * expect * @param compressedData The stream of compressed data to load locations and measured data points from */ - BinaryFormatDeserializer(final MetaData metaData, final InputStream compressedData) { + BinaryFormatDeserializer(final RequestMetaData metaData, final InputStream compressedData) { this.metaData = Objects.requireNonNull(metaData); this.compressedData = Objects.requireNonNull(compressedData); } diff --git a/libs/deserializer/src/main/java/de/cyface/deserializer/DeserializerFactory.java b/libs/deserializer/src/main/java/de/cyface/deserializer/DeserializerFactory.java index 961fe70..cc467a9 100644 --- a/libs/deserializer/src/main/java/de/cyface/deserializer/DeserializerFactory.java +++ b/libs/deserializer/src/main/java/de/cyface/deserializer/DeserializerFactory.java @@ -24,7 +24,7 @@ import java.util.List; import java.util.UUID; -import de.cyface.model.MetaData; +import de.cyface.model.RequestMetaData; /** * A collection of static factory methods to hide the possible complexity of {@link Deserializer} creation. @@ -45,7 +45,7 @@ private DeserializerFactory() { /** * Create a new {@link Deserializer} for a {@link de.cyface.model.Measurement} in Cyface Binary data with its * accompanying events. - * Both are provided as compressed input streams, together with the {@link MetaData} about the + * Both are provided as compressed input streams, together with the {@link RequestMetaData} about the * Measurement. * * @param metaData The meta information about the Measurement to load @@ -54,7 +54,7 @@ private DeserializerFactory() { * @return A Deserializer for the Cyface binary format * @throws IOException When writing data failed */ - public static BinaryFormatDeserializer create(final MetaData metaData, final InputStream compressedDataStream) throws IOException { + public static BinaryFormatDeserializer create(final RequestMetaData metaData, final InputStream compressedDataStream) throws IOException { return new BinaryFormatDeserializer(metaData, compressedDataStream); } diff --git a/libs/deserializer/src/main/java/de/cyface/deserializer/UnzippedPhoneDataDeserializer.java b/libs/deserializer/src/main/java/de/cyface/deserializer/UnzippedPhoneDataDeserializer.java index abbdca1..0b89d09 100644 --- a/libs/deserializer/src/main/java/de/cyface/deserializer/UnzippedPhoneDataDeserializer.java +++ b/libs/deserializer/src/main/java/de/cyface/deserializer/UnzippedPhoneDataDeserializer.java @@ -41,7 +41,7 @@ import de.cyface.model.Event; import de.cyface.model.Measurement; import de.cyface.model.MeasurementIdentifier; -import de.cyface.model.MetaData; +import de.cyface.model.RequestMetaData; import de.cyface.model.Modality; import de.cyface.model.Point3DImpl; import de.cyface.model.RawRecord; @@ -253,7 +253,7 @@ private List queryForEvents(final Connection connection, final Measuremen * default values * @throws SQLException If the query was not successful */ - private MetaData queryForMetaData(final Connection connection, final long measurementNumber) throws SQLException { + private RequestMetaData queryForMetaData(final Connection connection, final long measurementNumber) throws SQLException { final var deviceIdentifierQuery = connection.prepareStatement(DEVICE_IDENTIFIER_QUERY); final var deviceIdentifierResultSet = deviceIdentifierQuery.executeQuery(); @@ -268,7 +268,7 @@ private MetaData queryForMetaData(final Connection connection, final long measur final var lengthResultSet = lengthQuery.executeQuery(); lengthResultSet.next(); final var length = lengthResultSet.getDouble(1); - return new MetaData(measurementIdentifier, deviceType, osVersion, appVersion, length, userId, MetaData.CURRENT_VERSION); + return new RequestMetaData(measurementIdentifier, deviceType, osVersion, appVersion, length, userId, RequestMetaData.CURRENT_VERSION); } /** diff --git a/libs/deserializer/src/test/java/de/cyface/deserializer/BinaryFormatDeserializerTest.java b/libs/deserializer/src/test/java/de/cyface/deserializer/BinaryFormatDeserializerTest.java index 4601381..d980033 100644 --- a/libs/deserializer/src/test/java/de/cyface/deserializer/BinaryFormatDeserializerTest.java +++ b/libs/deserializer/src/test/java/de/cyface/deserializer/BinaryFormatDeserializerTest.java @@ -58,7 +58,7 @@ import de.cyface.deserializer.exceptions.InvalidLifecycleEvents; import de.cyface.model.Event; import de.cyface.model.MeasurementIdentifier; -import de.cyface.model.MetaData; +import de.cyface.model.RequestMetaData; import de.cyface.model.Modality; import de.cyface.model.Point3D; import de.cyface.model.Point3DImpl; @@ -112,9 +112,9 @@ void test() throws IOException, InvalidLifecycleEvents, UnsupportedFileVersion { // Arrange final var identifier = new MeasurementIdentifier("test", 1); try (final var testData = testData(identifier)) { - final var metaData = new MetaData(identifier, "Pixel 3", "Android 9.0.0", "1.2.0-beta1", 500.5, + final var metaData = new RequestMetaData(identifier, "Pixel 3", "Android 9.0.0", "1.2.0-beta1", 500.5, TEST_USER_ID, - MetaData.CURRENT_VERSION); + RequestMetaData.CURRENT_VERSION); final var reader = new BinaryFormatDeserializer(metaData, testData); // Act @@ -128,7 +128,7 @@ void test() throws IOException, InvalidLifecycleEvents, UnsupportedFileVersion { assertThat(result.getMetaData().getAppVersion(), is("1.2.0-beta1")); assertThat(result.getMetaData().getLength(), is(500.5)); assertThat(result.getMetaData().getUserId(), is(TEST_USER_ID)); - assertThat(result.getMetaData().getVersion(), is(MetaData.CURRENT_VERSION)); + assertThat(result.getMetaData().getVersion(), is(RequestMetaData.CURRENT_VERSION)); final var resultTracks = result.getTracks(); assertThat(resultTracks, hasSize(3)); @@ -336,8 +336,8 @@ void testSerializeDeserialize() throws IOException, InvalidLifecycleEvents { final var directions = Point3DDeserializer .directions(parsedMeasurement.getDirectionsBinary().getDirectionsList()); final var trackBuilder = new TrackBuilder(); - final var metaData = new MetaData(identifier, "Pixel 3", "Android 12.0.0", "3.0.2", 0.0, - TEST_USER_ID, MetaData.CURRENT_VERSION); + final var metaData = new RequestMetaData(identifier, "Pixel 3", "Android 12.0.0", "3.0.2", 0.0, + TEST_USER_ID, RequestMetaData.CURRENT_VERSION); final var tracks = trackBuilder.build(deserializedLocations, deserializedEvents, accelerations, rotations, directions, identifier); final var deserializedMeasurement = new de.cyface.model.Measurement(metaData, tracks); diff --git a/libs/model/src/main/java/de/cyface/model/Measurement.java b/libs/model/src/main/java/de/cyface/model/Measurement.java index 2caa0b4..e7836a5 100644 --- a/libs/model/src/main/java/de/cyface/model/Measurement.java +++ b/libs/model/src/main/java/de/cyface/model/Measurement.java @@ -67,7 +67,7 @@ public class Measurement implements Serializable { /** * The context of this {@code Measurement}. */ - private MetaData metaData; + private RequestMetaData metaData; /** * The data collected for this {@code Measurement} in {@code Track}-slices, ordered by timestamp. */ @@ -88,7 +88,7 @@ public Measurement() { * @param metaData The context of this {@code Measurement}. * @param tracks The data collected for this {@code Measurement} in {@code Track}-slices, ordered by timestamp. */ - public Measurement(final MetaData metaData, final List tracks) { + public Measurement(final RequestMetaData metaData, final List tracks) { Validate.notNull(metaData); this.metaData = metaData; @@ -156,7 +156,7 @@ private List tracks(final List trackBuckets) { /** * @return The context of this {@code Measurement}. */ - public MetaData getMetaData() { + public RequestMetaData getMetaData() { return metaData; } @@ -173,7 +173,7 @@ public List getTracks() { * @param metaData The context of this {@code Measurement}. */ @SuppressWarnings("unused") // Required by Apache Flink. - public void setMetaData(final MetaData metaData) { + public void setMetaData(final RequestMetaData metaData) { this.metaData = metaData; } @@ -343,7 +343,7 @@ public void asJson(final String username, final Consumer handler) { handler.accept("}"); } - private Json.JsonObject asJson(final String username, final MetaData metaData) { + private Json.JsonObject asJson(final String username, final RequestMetaData metaData) { return jsonObject( jsonKeyValue("userId", metaData.getUserId().toString()), jsonKeyValue("username", username), @@ -511,7 +511,7 @@ public static void csvHeader(final ExportOptions options, final Consumer * @param totalTravelTime the time traveled so far * @return the csv row as String */ - private String csvRow(ExportOptions options, final String username, final MetaData metaData, + private String csvRow(ExportOptions options, final String username, final RequestMetaData metaData, final RawRecord locationRecord, final int trackId, final double modalityTypeDistance, final double totalDistance, final long modalityTypeTravelTime, final long totalTravelTime) { @@ -539,7 +539,7 @@ private String csvRow(ExportOptions options, final String username, final MetaDa return String.join(",", elements); } - private String csvSensorRow(ExportOptions options, final String username, final MetaData metaData, + private String csvSensorRow(ExportOptions options, final String username, final RequestMetaData metaData, final Point3DImpl pointRecord, final int trackId) { diff --git a/libs/model/src/main/java/de/cyface/model/MetaData.java b/libs/model/src/main/java/de/cyface/model/MetaData.java index 2ef154c..9012ac6 100644 --- a/libs/model/src/main/java/de/cyface/model/MetaData.java +++ b/libs/model/src/main/java/de/cyface/model/MetaData.java @@ -40,7 +40,7 @@ public class MetaData implements Serializable { */ public static final String CURRENT_VERSION = "3.0.0"; /** - * Regex of supported {@link MetaData} versions of this class. + * Regex of supported {@link RequestMetaData} versions of this class. */ public static final String SUPPORTED_VERSIONS = "3.0.0"; /** @@ -244,7 +244,7 @@ public boolean equals(Object o) { return true; if (o == null || getClass() != o.getClass()) return false; - MetaData metaData = (MetaData)o; + RequestMetaData metaData = (RequestMetaData)o; return Double.compare(metaData.length, length) == 0 && identifier.equals(metaData.identifier) && deviceType.equals(metaData.deviceType) && diff --git a/libs/model/src/main/java/de/cyface/model/TrackBucket.java b/libs/model/src/main/java/de/cyface/model/TrackBucket.java index fa24657..071fa9b 100644 --- a/libs/model/src/main/java/de/cyface/model/TrackBucket.java +++ b/libs/model/src/main/java/de/cyface/model/TrackBucket.java @@ -43,9 +43,9 @@ public class TrackBucket { */ final Track track; /** - * The {@link MetaData} of the track. + * The {@link RequestMetaData} of the track. */ - final MetaData metaData; + final RequestMetaData metaData; /** * Initialized a fully constructed instance of this class. @@ -53,9 +53,9 @@ public class TrackBucket { * @param trackId The track's position within the measurement. * @param bucket The time "slice" of the bucket. * @param track The track slice of the bucket. - * @param metaData The {@link MetaData} of the track. + * @param metaData The {@link RequestMetaData} of the track. */ - public TrackBucket(final int trackId, final Date bucket, final Track track, final MetaData metaData) { + public TrackBucket(final int trackId, final Date bucket, final Track track, final RequestMetaData metaData) { this.trackId = trackId; this.bucket = bucket; this.track = track; @@ -84,9 +84,9 @@ public Track getTrack() { } /** - * @return The {@link MetaData} of the track. + * @return The {@link RequestMetaData} of the track. */ - public MetaData getMetaData() { + public RequestMetaData getMetaData() { return metaData; } } diff --git a/libs/model/src/main/kotlin/de/cyface/model/RequestMetaData.kt b/libs/model/src/main/kotlin/de/cyface/model/RequestMetaData.kt index 3855567..c859062 100644 --- a/libs/model/src/main/kotlin/de/cyface/model/RequestMetaData.kt +++ b/libs/model/src/main/kotlin/de/cyface/model/RequestMetaData.kt @@ -19,151 +19,301 @@ package de.cyface.model +import de.cyface.model.RequestMetaData.MeasurementMetaData.GeoLocation import java.io.Serializable import java.nio.charset.Charset /** - * The metadata as transmitted in the request header or pre-request body. + * The metadata as transmitted in the request header or pre-request body for the measurement file. * * @author Armin Schnabel - * @property deviceType The worldwide unique identifier of the device uploading the data. - * @property measurementIdentifier The device wide unique identifier of the uploaded measurement. - * @property operatingSystemVersion The operating system version, such as Android 9.0.0 or iOS 11.2. - * @property deviceType The type of device uploading the data, such as Pixel 3 or iPhone 6 Plus. - * @property applicationVersion The version of the app that transmitted the measurement. - * @property length The length of the measurement in meters. - * @property locationCount The count of geolocations in the transmitted measurement. - * @property startLocation The `GeoLocation` at the beginning of the track represented by the transmitted measurement. - * @property endLocation The `GeoLocation` at the end of the track represented by the transmitted measurement. - * @property modality The modality type used to capture the measurement. - * @property formatVersion The format version of the upload file. - * @property logCount Count of log files captured for this measurement, e.g. metrics captured during image capturing. - * @property imageCount Count of images captured for this measurement. Allows to notice when all images are transmitted. - * @property videoCount Count of videos captured for this measurement. Allows to notice when all videos are transmitted. - * @property filesSize The number of bytes of the files collected for this measurement (log, image and video data). - * @property attachmentIdentifier The identifier of the attachment, if this measurement is an attachment. + * @property identifier The identifier which identifies the data object transmitted. */ @Suppress("unused") // Part of the API -data class RequestMetaData( - val deviceIdentifier: String, - val measurementIdentifier: String, - val operatingSystemVersion: String, - val deviceType: String, - val applicationVersion: String, - val length: Double, - val locationCount: Long, - val startLocation: GeoLocation?, - val endLocation: GeoLocation?, - val modality: String, - val formatVersion: Int, - val logCount: Int, - val imageCount: Int, - val videoCount: Int, - val filesSize: Long, - val attachmentIdentifier: String? = null, +data class RequestMetaData( + val identifier: T, + val deviceMetaData: DeviceMetaData, + val applicationMetaData: ApplicationMetaData, + val measurementMetaData: MeasurementMetaData, + val attachmentMetaData: AttachmentMetaData, ) : Serializable { + /** + * An identifier which is passed in [RequestMetaData] to identify which data object is transmitted. + * + * @author Armin Schnabel + */ + interface Identifier : Serializable { + companion object { + /** + * The length of a universal unique identifier. + */ + const val UUID_LENGTH = 36 - init { - require(deviceIdentifier.toByteArray(Charset.forName(DEFAULT_CHARSET)).size == UUID_LENGTH) { - "Field deviceId was not exactly 128 Bit, which is required for UUIDs!" - } - require(deviceType.isNotEmpty() && deviceType.length <= MAX_GENERIC_METADATA_FIELD_LENGTH) { - "Field deviceType had an invalid length of ${deviceType.length.toLong()}" + /** + * The default char set to use for encoding and decoding strings transmitted as metadata. + */ + const val DEFAULT_CHARSET = "UTF-8" + + /** + * The maximum length of the measurement or attachment identifier in characters (this is the amount of + * characters of {@value Long#MAX_VALUE}). + */ + const val MAX_ID_LENGTH = 20 } - require(measurementIdentifier.isNotEmpty() && measurementIdentifier.length <= MAX_ID_LENGTH) { - "Field measurementId had an invalid length of ${measurementIdentifier.length.toLong()}" + } + + /** + * An identifier for a transmitted measurement file. + * + * @author Armin Schnabel + * @property deviceId The world-unique identifier of the device which collected the data. + * @property measurementId The device-unique identifier of the measurement transmitted. + */ + data class MeasurementIdentifier( + val deviceId: String, + val measurementId: String, + ) : Identifier { + + init { + require( + deviceId.toByteArray(Charset.forName(Identifier.DEFAULT_CHARSET)).size == Identifier.UUID_LENGTH + ) { + "Field deviceId was not exactly 128 Bit, which is required for UUIDs!" + } + require(measurementId.isNotEmpty() && measurementId.length <= Identifier.MAX_ID_LENGTH) { + "Field measurementId had an invalid length of ${measurementId.length.toLong()}" + } } - require(operatingSystemVersion.isNotEmpty() && - operatingSystemVersion.length <= MAX_GENERIC_METADATA_FIELD_LENGTH) { - "Field osVersion had an invalid length of ${operatingSystemVersion.length.toLong()}" + + companion object { + /** + * Used to serialize objects of this class. Only change this value if this classes attribute set changes. + */ + @Suppress("ConstPropertyName") + private const val serialVersionUID = 1L } - require(applicationVersion.isNotEmpty() && applicationVersion.length <= MAX_GENERIC_METADATA_FIELD_LENGTH) { - "Field applicationVersion had an invalid length of ${applicationVersion.length.toLong()}" + } + + /** + * An identifier for a transmitted attachment file. + * + * @author Armin Schnabel + * @property attachmentId The device-unique identifier of the attachment file transmitted. + */ + data class AttachmentIdentifier( + val attachmentId: String, + ) : Identifier { + init { + require(attachmentId.isNotEmpty() && attachmentId.length <= Identifier.MAX_ID_LENGTH) { + "Field attachmentId had an invalid length of ${attachmentId.length.toLong()}" + } } - require(length >= MINIMUM_TRACK_LENGTH) { - "Field length had an invalid value smaller then 0.0: $length" + + companion object { + /** + * Used to serialize objects of this class. Only change this value if this classes attribute set changes. + */ + @Suppress("ConstPropertyName") + private const val serialVersionUID = 1L } - require(locationCount >= MINIMUM_LOCATION_COUNT) { - "Field locationCount had an invalid value smaller then 0: $locationCount" + } + + /** + * The metadata which describes the device which collected the data. + * + * @author Armin Schnabel + * @property operatingSystemVersion The operating system version, such as Android 9.0.0 or iOS 11.2. + * @property deviceType The type of device uploading the data, such as Pixel 3 or iPhone 6 Plus. + */ + data class DeviceMetaData( + val operatingSystemVersion: String, + val deviceType: String, + ) : Serializable { + init { + require( + operatingSystemVersion.isNotEmpty() && + operatingSystemVersion.length <= MAX_GENERIC_METADATA_FIELD_LENGTH + ) { + "Field osVersion had an invalid length of ${operatingSystemVersion.length.toLong()}" + } + require(deviceType.isNotEmpty() && deviceType.length <= MAX_GENERIC_METADATA_FIELD_LENGTH) { + "Field deviceType had an invalid length of ${deviceType.length.toLong()}" + } } - require(locationCount == MINIMUM_LOCATION_COUNT || startLocation != null) { - "Start location should only be defined if there is at least one location in the uploaded track!" + + companion object { + /** + * Used to serialize objects of this class. Only change this value if this classes attribute set changes. + */ + @Suppress("ConstPropertyName") + private const val serialVersionUID = 1L } - require(locationCount == MINIMUM_LOCATION_COUNT || endLocation != null) { - "End location should only be defined if there is at least one location in the uploaded track!" + } + + /** + * The metadata which describes the application which collected the data. + * + * @author Armin Schnabel + * @property applicationVersion The version of the app that transmitted the measurement. + * @property formatVersion The format version of the upload file. + */ + data class ApplicationMetaData( + val applicationVersion: String, + val formatVersion: Int, + ) : Serializable { + init { + require(applicationVersion.isNotEmpty() && applicationVersion.length <= MAX_GENERIC_METADATA_FIELD_LENGTH) { + "Field applicationVersion had an invalid length of ${applicationVersion.length.toLong()}" + } + require(formatVersion == CURRENT_TRANSFER_FILE_FORMAT_VERSION) { + "Unsupported formatVersion: ${formatVersion.toLong()}" + } } - require(modality.isNotEmpty() && modality.length <= MAX_GENERIC_METADATA_FIELD_LENGTH) { - "Field modality had an invalid length of ${modality.length.toLong()}" + + companion object { + /** + * Used to serialize objects of this class. Only change this value if this classes attribute set changes. + */ + @Suppress("ConstPropertyName") + private const val serialVersionUID = 1L + + /** + * The current version of the transferred file. This is always specified by the first two bytes of the file + * transferred and helps compatible APIs to process data from different client versions. + */ + const val CURRENT_TRANSFER_FILE_FORMAT_VERSION = 3 } - require(formatVersion == CURRENT_TRANSFER_FILE_FORMAT_VERSION) { - "Unsupported formatVersion: ${formatVersion.toLong()}" + } + + /** + * The metadata which describes the measurement the data was collected for. + * + * @author Armin Schnabel + * @property length The length of the measurement in meters. + * @property locationCount The count of geolocations in the transmitted measurement. + * @property startLocation The first [GeoLocation] captured by the transmitted measurement. + * @property endLocation The last [GeoLocation] captured by the transmitted measurement. + * @property modality The modality type used to capture the measurement. + */ + data class MeasurementMetaData( + val length: Double, + val locationCount: Long, + val startLocation: GeoLocation?, + val endLocation: GeoLocation?, + val modality: String, + ) : Serializable { + init { + require(length >= MINIMUM_TRACK_LENGTH) { + "Field length had an invalid value smaller then 0.0: $length" + } + require(locationCount >= MINIMUM_LOCATION_COUNT) { + "Field locationCount had an invalid value smaller then 0: $locationCount" + } + require(locationCount == MINIMUM_LOCATION_COUNT || startLocation != null) { + "Start location should only be defined if there is at least one location in the uploaded track!" + } + require(locationCount == MINIMUM_LOCATION_COUNT || endLocation != null) { + "End location should only be defined if there is at least one location in the uploaded track!" + } + require(modality.isNotEmpty() && modality.length <= MAX_GENERIC_METADATA_FIELD_LENGTH) { + "Field modality had an invalid length of ${modality.length.toLong()}" + } } - require(logCount >= 0) { "Invalid logCount: $logCount" } - require(imageCount >= 0) { "Invalid imageCount: $imageCount" } - require(videoCount >= 0) { "Invalid videoCount: $videoCount" } - require(filesSize >= 0) { "Invalid filesSize: $filesSize" } - require(attachmentIdentifier == null || attachmentIdentifier.length <= MAX_ID_LENGTH) { - "Field attachmentId had an invalid length of ${attachmentIdentifier!!.length.toLong()}" + + /** + * This class represents a geolocation at the start or end of a track. + * + * @author Armin Schnabel + * @property timestamp The Unix timestamp this location was captured on in milliseconds. + * @property latitude Geographical latitude (decimal fraction) raging from -90° (south) to 90° (north). + * @property longitude Geographical longitude (decimal fraction) ranging from -180° (west) to 180° (east). + */ + data class GeoLocation( + val timestamp: Long, + val latitude: Double, + val longitude: Double + ) + + companion object { + /** + * Used to serialize objects of this class. Only change this value if this classes attribute set changes. + */ + @Suppress("ConstPropertyName") + private const val serialVersionUID = 1L + + /** + * The minimum length of a track stored with a measurement. + */ + private const val MINIMUM_TRACK_LENGTH = 0.0 + + /** + * The minimum valid amount of locations stored inside a measurement. + */ + private const val MINIMUM_LOCATION_COUNT = 0L } } /** - * This class represents a geolocation at the start or end of a track. + * The metadata which describes the attachments which were collected together with the measurement. * * @author Armin Schnabel - * @property timestamp The timestamp this location was captured on in milliseconds since 1st January 1970 (epoch). - * @property latitude Geographical latitude (decimal fraction) raging from -90° (south) to 90° (north). - * @property longitude Geographical longitude (decimal fraction) ranging from -180° (west) to 180° (east). + * @property logCount Number of log files captured for this measurement, e.g. image capturing metrics. + * @property imageCount Number of image files captured for this measurement. + * @property videoCount Number of video files captured for this measurement. + * @property filesSize The number of bytes of the files collected for this measurement (log, image and video data). */ - data class GeoLocation( - val timestamp: Long, - val latitude: Double, - val longitude: Double - ) + data class AttachmentMetaData( + val logCount: Int, + val imageCount: Int, + val videoCount: Int, + val filesSize: Long, + ) : Serializable { + init { + require(logCount >= 0) { "Invalid logCount: $logCount" } + require(imageCount >= 0) { "Invalid imageCount: $imageCount" } + require(videoCount >= 0) { "Invalid videoCount: $videoCount" } + require(filesSize >= 0) { "Invalid filesSize: $filesSize" } + } + + companion object { + /** + * Used to serialize objects of this class. Only change this value if this classes attribute set changes. + */ + @Suppress("ConstPropertyName") + private const val serialVersionUID = 1L + } + } companion object { /** * Used to serialize objects of this class. Only change this value if this classes attribute set changes. */ @Suppress("ConstPropertyName") - private const val serialVersionUID = -1700430112854515404L - - /** - * The length of a universal unique identifier. - */ - private const val UUID_LENGTH = 36 - - /** - * The default char set to use for encoding and decoding strings transmitted as metadata. - */ - private const val DEFAULT_CHARSET = "UTF-8" + private const val serialVersionUID = 2L /** * Maximum size of a metadata field, with plenty space for future development. Prevents attackers from putting * arbitrary long data into these fields. */ const val MAX_GENERIC_METADATA_FIELD_LENGTH = 30 + } - /** - * The maximum length of the measurement or attachment identifier in characters (this is the amount of - * characters of {@value Long#MAX_VALUE}). - */ - private const val MAX_ID_LENGTH = 20 + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false - /** - * The minimum length of a track stored with a measurement. - */ - private const val MINIMUM_TRACK_LENGTH = 0.0 + other as RequestMetaData<*> - /** - * The minimum valid amount of locations stored inside a measurement. - */ - private const val MINIMUM_LOCATION_COUNT = 0L + if (identifier != other.identifier) return false + if (deviceMetaData != other.deviceMetaData) return false + if (applicationMetaData != other.applicationMetaData) return false + if (measurementMetaData != other.measurementMetaData) return false + if (attachmentMetaData != other.attachmentMetaData) return false - /** - * The current version of the transferred file. This is always specified by the first two bytes of the file - * transferred and helps compatible APIs to process data from different client versions. - */ - const val CURRENT_TRANSFER_FILE_FORMAT_VERSION = 3 + return true + } + + override fun hashCode(): Int { + return identifier.hashCode() } } diff --git a/libs/model/src/test/java/de/cyface/model/MeasurementTest.java b/libs/model/src/test/java/de/cyface/model/MeasurementTest.java index 02794fd..e4daac1 100644 --- a/libs/model/src/test/java/de/cyface/model/MeasurementTest.java +++ b/libs/model/src/test/java/de/cyface/model/MeasurementTest.java @@ -426,9 +426,9 @@ private double accuracy(final int index) { /** * @return A static set of metadata to be used by test {@link Measurement} instances */ - private static MetaData metaData() { - return new MetaData(new MeasurementIdentifier(DEVICE_IDENTIFIER, MEASUREMENT_IDENTIFIER), + private static RequestMetaData metaData() { + return new RequestMetaData(new MeasurementIdentifier(DEVICE_IDENTIFIER, MEASUREMENT_IDENTIFIER), "Android SDK built for x86", "Android 8.0.0", - "2.7.0-beta1", 0.0, TEST_USER_ID, MetaData.CURRENT_VERSION); + "2.7.0-beta1", 0.0, TEST_USER_ID, RequestMetaData.CURRENT_VERSION); } }