diff --git a/.pubnub.yml b/.pubnub.yml index 81fb52b57..907e1d764 100644 --- a/.pubnub.yml +++ b/.pubnub.yml @@ -1,9 +1,9 @@ name: java -version: 6.4.3 +version: 6.4.4 schema: 1 scm: github.com/pubnub/java files: - - build/libs/pubnub-gson-6.4.3-all.jar + - build/libs/pubnub-gson-6.4.4-all.jar sdks: - type: library @@ -23,8 +23,8 @@ sdks: - distribution-type: library distribution-repository: maven - package-name: pubnub-gson-6.4.3 - location: https://repo.maven.apache.org/maven2/com/pubnub/pubnub-gson/6.4.3/pubnub-gson-6.4.3.jar + package-name: pubnub-gson-6.4.4 + location: https://repo.maven.apache.org/maven2/com/pubnub/pubnub-gson/6.4.4/pubnub-gson-6.4.4.jar supported-platforms: supported-operating-systems: Android: @@ -100,11 +100,11 @@ sdks: license-url: https://github.com/google/gson/blob/gson-parent-2.9.0/LICENSE is-required: Required - - name: jackson-databind - min-version: 2.14.2 - location: https://repo.maven.apache.org/maven2/com/fasterxml/jackson/core/jackson-databind/2.14.2/jackson-databind-2.14.2.jar - license: Apache License, Version 2.0 - license-url: hhttps://github.com/FasterXML/jackson-databind/blob/jackson-databind-2.14.2/README.md + name: cbor + min-version: "0.9" + location: https://repo.maven.apache.org/maven2/co/nstant/in/cbor/0.9 + license: The Apache Software License, Version 2.0 + license-url: https://www.apache.org/licenses/LICENSE-2.0.txt is-required: Required - name: json @@ -115,6 +115,11 @@ sdks: is-required: Required changelog: + - date: 2023-11-30 + version: v6.4.4 + changes: + - type: bug + text: "Bring back compatibility with Android 6+ by removing the Jackson library dependency." - date: 2023-11-28 version: v6.4.3 changes: diff --git a/CHANGELOG.md b/CHANGELOG.md index f0226aefc..6fb559739 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## v6.4.4 +November 30 2023 + +#### Fixed +- Bring back compatibility with Android 6+ by removing the Jackson library dependency. + ## v6.4.3 November 28 2023 diff --git a/README.md b/README.md index 844442896..a571cd500 100644 --- a/README.md +++ b/README.md @@ -22,13 +22,13 @@ You will need the publish and subscribe keys to authenticate your app. Get your com.pubnub pubnub-gson - 6.4.3 + 6.4.4 ``` * for Gradle, add the following dependency in your `gradle.build`: ```groovy - implementation 'com.pubnub:pubnub-gson:6.4.3' + implementation 'com.pubnub:pubnub-gson:6.4.4' ``` 2. Configure your keys: diff --git a/build.gradle b/build.gradle index c3198607c..42388c3ee 100644 --- a/build.gradle +++ b/build.gradle @@ -10,7 +10,7 @@ plugins { } group = 'com.pubnub' -version = '6.4.3' +version = '6.4.4' description = """""" @@ -56,8 +56,8 @@ dependencies { implementation group: 'com.squareup.retrofit2', name: 'converter-gson', version: '2.6.2' // cbor - implementation 'com.fasterxml.jackson.core:jackson-databind:2.14.2' - implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-cbor:2.14.2' + implementation 'co.nstant.in:cbor:0.9' + implementation 'org.jetbrains:annotations:23.0.0' diff --git a/gradle.properties b/gradle.properties index 827b176fc..abe45b364 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,7 +3,7 @@ SONATYPE_HOST=DEFAULT SONATYPE_AUTOMATIC_RELEASE=true GROUP=com.pubnub POM_ARTIFACT_ID=pubnub-gson -VERSION_NAME=6.4.3 +VERSION_NAME=6.4.4 POM_PACKAGING=jar POM_NAME=PubNub Java SDK diff --git a/src/main/java/com/pubnub/api/PubNub.java b/src/main/java/com/pubnub/api/PubNub.java index bf1a42c29..1928b77e0 100644 --- a/src/main/java/com/pubnub/api/PubNub.java +++ b/src/main/java/com/pubnub/api/PubNub.java @@ -105,7 +105,7 @@ public class PubNub { private static final int TIMESTAMP_DIVIDER = 1000; private static final int MAX_SEQUENCE = 65535; - private static final String SDK_VERSION = "6.4.3"; + private static final String SDK_VERSION = "6.4.4"; private final ListenerManager listenerManager; private final StateManager stateManager; diff --git a/src/main/java/com/pubnub/api/endpoints/files/PublishFileMessage.java b/src/main/java/com/pubnub/api/endpoints/files/PublishFileMessage.java index 63c290817..5c335756e 100644 --- a/src/main/java/com/pubnub/api/endpoints/files/PublishFileMessage.java +++ b/src/main/java/com/pubnub/api/endpoints/files/PublishFileMessage.java @@ -86,7 +86,7 @@ protected void validateParams() throws PubNubException { @Override protected Call> doWork(Map baseParams) throws PubNubException { - String stringifiedMessage = mapper.toJsonUsinJackson(new FileUploadNotification(this.message, pnFile)); + String stringifiedMessage = mapper.toJson(new FileUploadNotification(this.message, pnFile)); String messageAsString; CryptoModule cryptoModule = getPubnub().getCryptoModule(); if (cryptoModule != null) { @@ -99,7 +99,7 @@ protected Call> doWork(Map baseParams) throws PubNu final HashMap params = new HashMap<>(baseParams); if (meta != null) { - String stringifiedMeta = mapper.toJsonUsinJackson(meta); + String stringifiedMeta = mapper.toJson(meta); stringifiedMeta = PubNubUtil.urlEncode(stringifiedMeta); params.put("meta", stringifiedMeta); } diff --git a/src/main/java/com/pubnub/api/managers/MapperManager.java b/src/main/java/com/pubnub/api/managers/MapperManager.java index 6d214cce5..94b6ea47f 100644 --- a/src/main/java/com/pubnub/api/managers/MapperManager.java +++ b/src/main/java/com/pubnub/api/managers/MapperManager.java @@ -1,7 +1,5 @@ package com.pubnub.api.managers; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonArray; @@ -29,7 +27,9 @@ import java.io.IOException; import java.lang.reflect.Type; import java.util.Iterator; +import java.util.List; import java.util.Map; +import java.util.Set; public class MapperManager { @@ -38,8 +38,6 @@ public class MapperManager { @Getter private final Converter.Factory converterFactory; - private final ObjectMapper jacksonObjectMapper = new ObjectMapper(); - public MapperManager() { TypeAdapter booleanAsIntAdapter = getBooleanTypeAdapter(); @@ -150,7 +148,15 @@ public JsonElement toJsonTree(Object object) { public String toJson(Object input) throws PubNubException { try { - return this.objectMapper.toJson(input); + if (input instanceof List && input.getClass().isAnonymousClass()) { + return this.objectMapper.toJson(input, List.class); + } else if (input instanceof Map && input.getClass().isAnonymousClass()) { + return this.objectMapper.toJson(input, Map.class); + } else if (input instanceof Set && input.getClass().isAnonymousClass()) { + return this.objectMapper.toJson(input, Set.class); + } else { + return this.objectMapper.toJson(input); + } } catch (JsonParseException e) { throw PubNubException.builder() .pubnubError(PubNubErrorBuilder.PNERROBJ_JSON_ERROR) @@ -160,18 +166,6 @@ public String toJson(Object input) throws PubNubException { } } - public String toJsonUsinJackson(Object input) throws PubNubException { - try { - return this.jacksonObjectMapper.writeValueAsString(input); - } catch (JsonProcessingException e) { - throw PubNubException.builder() - .pubnubError(PubNubErrorBuilder.PNERROBJ_JSON_ERROR) - .errormsg(e.getMessage()) - .cause(e) - .build(); - } - } - @NotNull private TypeAdapter getBooleanTypeAdapter() { return new TypeAdapter() { diff --git a/src/main/java/com/pubnub/api/managers/token_manager/TokenParser.java b/src/main/java/com/pubnub/api/managers/token_manager/TokenParser.java deleted file mode 100644 index 59623923e..000000000 --- a/src/main/java/com/pubnub/api/managers/token_manager/TokenParser.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.pubnub.api.managers.token_manager; - -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.Nulls; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.dataformat.cbor.CBORFactory; -import com.pubnub.api.PubNubException; -import com.pubnub.api.models.consumer.access_manager.v3.PNToken; -import com.pubnub.api.vendor.Base64; - -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.util.Map; - -import static com.pubnub.api.builder.PubNubErrorBuilder.PNERROBJ_INVALID_ACCESS_TOKEN; - -public class TokenParser { - private final ObjectMapper mapper = objectMapper(); - - public PNToken unwrapToken(String token) throws PubNubException { - try { - byte[] byteArray = Base64.decode(token.getBytes(StandardCharsets.UTF_8), Base64.URL_SAFE); - return mapper.readValue(byteArray, PNToken.class); - } catch (IOException e) { - throw PubNubException.builder() - .cause(e) - .pubnubError(PNERROBJ_INVALID_ACCESS_TOKEN) - .build(); - } - } - - private ObjectMapper objectMapper() { - ObjectMapper objectMapper = new ObjectMapper(new CBORFactory()); - objectMapper.configOverride(Map.class).setSetterInfo(JsonSetter.Value.forValueNulls(Nulls.AS_EMPTY)); - objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - return objectMapper; - } -} diff --git a/src/main/java/com/pubnub/api/managers/token_manager/TokenParser.kt b/src/main/java/com/pubnub/api/managers/token_manager/TokenParser.kt new file mode 100644 index 000000000..4262cc598 --- /dev/null +++ b/src/main/java/com/pubnub/api/managers/token_manager/TokenParser.kt @@ -0,0 +1,110 @@ +package com.pubnub.api.managers.token_manager + +import co.nstant.`in`.cbor.CborDecoder +import co.nstant.`in`.cbor.model.ByteString +import co.nstant.`in`.cbor.model.NegativeInteger +import co.nstant.`in`.cbor.model.UnsignedInteger +import com.pubnub.api.PubNubException +import com.pubnub.api.builder.PubNubErrorBuilder +import com.pubnub.api.models.consumer.access_manager.v3.PNToken +import com.pubnub.api.vendor.Base64 +import java.math.BigInteger +import java.nio.charset.StandardCharsets +import kotlin.collections.Map +import kotlin.collections.component1 +import kotlin.collections.component2 +import kotlin.collections.set +import co.nstant.`in`.cbor.model.Map as CborMap + +internal class TokenParser { + + private fun getException(message: String) = PubNubException( + message, PubNubErrorBuilder.PNERROBJ_INVALID_ACCESS_TOKEN, null, null, 0, null, null + ) + + @Throws(PubNubException::class) + fun unwrapToken(token: String): PNToken { + val byteArray = Base64.decode(token.toByteArray(StandardCharsets.UTF_8), Base64.URL_SAFE) + val firstElement = CborDecoder(byteArray.inputStream()).decode().firstOrNull() ?: throw getException("Empty token") + + val firstLevelMap = (firstElement as? CborMap)?.toJvmMap() ?: throw getException("First element is not a map") + val version = firstLevelMap[VERSION_KEY]?.toString()?.toInt() ?: throw getException("Couldn't parse version") + val timestamp = firstLevelMap[TIMESTAMP_KEY]?.toString()?.toLong() ?: throw getException("Couldn't parse timestamp") + val ttl = firstLevelMap[TTL_KEY]?.toString()?.toLong() ?: throw getException("Couldn't parse ttl") + val resourcesValue = firstLevelMap[RESOURCES_KEY] as? Map<*, *> ?: throw getException("Resources are not present or are not map") + val patternsValue = firstLevelMap[PATTERNS_KEY] as? Map<*, *> ?: throw getException("Patterns are not present or are not map") + + return try { + PNToken.of( + version, + timestamp, + ttl, + resourcesValue.toPNTokenResources(), + patternsValue.toPNTokenResources(), + firstLevelMap[AUTHORIZED_UUID_KEY]?.toString(), + firstLevelMap[META_KEY], + ) + } catch (e: Exception) { + if (e is PubNubException) throw e + throw getException("Couldn't parse token: ${e.message}") + } + } + + private fun CborMap.toJvmMap(depth: Int = 0): MutableMap { + if (depth > 3) { + throw getException("Token is too deep") + } + val result = mutableMapOf() + for (key in this.keys) { + val value = this.get(key) + val keyString = when (key) { + is ByteString -> key.bytes.toString(StandardCharsets.UTF_8) + else -> key.toString() + } + + when (value) { + is CborMap -> result[keyString] = value.toJvmMap(depth + 1) + is ByteString -> result[keyString] = value.bytes + is List<*> -> result[keyString] = value.map { it.toString() } + is UnsignedInteger -> result[keyString] = value.value + is NegativeInteger -> result[keyString] = value.value + else -> result[keyString] = value.toString() + } + } + return result + } + + private fun Map<*, *>.toMapOfStringToInt(): Map { + return mapNotNull { (k, v) -> + when (v) { + is BigInteger -> k.toString() to v.toInt() + else -> v.toString().toIntOrNull()?.let { k.toString() to it } + } + }.toMap() + } + + private fun Map<*, *>.toPNTokenResources(): PNToken.PNTokenResources { + val channels = (this[CHANNELS_KEY] as? Map<*, *>)?.toMapOfStringToInt() ?: emptyMap() + val groups = (this[GROUPS_KEY] as? Map<*, *>)?.toMapOfStringToInt() ?: emptyMap() + val uuids = (this[UUIDS_KEY] as? Map<*, *>)?.toMapOfStringToInt() ?: emptyMap() + + return PNToken.PNTokenResources.of( + channels.mapValues { (_, v) -> PNToken.PNResourcePermissions.of(v) }, + groups.mapValues { (_, v) -> PNToken.PNResourcePermissions.of(v) }, + uuids.mapValues { (_, v) -> PNToken.PNResourcePermissions.of(v) } + ) + } + + companion object { + private const val VERSION_KEY = "v" + private const val TIMESTAMP_KEY = "t" + private const val TTL_KEY = "ttl" + private const val AUTHORIZED_UUID_KEY = "uuid" + private const val RESOURCES_KEY = "res" + private const val PATTERNS_KEY = "pat" + private const val META_KEY = "meta" + private const val CHANNELS_KEY = "chan" + private const val GROUPS_KEY = "grp" + private const val UUIDS_KEY = "uuid" + } +} diff --git a/src/main/java/com/pubnub/api/models/consumer/access_manager/v3/PNToken.java b/src/main/java/com/pubnub/api/models/consumer/access_manager/v3/PNToken.java index 15e1e4a44..8f18d25f1 100644 --- a/src/main/java/com/pubnub/api/models/consumer/access_manager/v3/PNToken.java +++ b/src/main/java/com/pubnub/api/models/consumer/access_manager/v3/PNToken.java @@ -1,8 +1,5 @@ package com.pubnub.api.models.consumer.access_manager.v3; -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; import com.pubnub.api.models.TokenBitmask; import lombok.Data; import lombok.NonNull; @@ -10,7 +7,6 @@ import java.util.Map; @Data -@JsonIgnoreProperties(ignoreUnknown = true) public class PNToken { private final int version; private final long timestamp; @@ -22,20 +18,18 @@ public class PNToken { @NonNull private final PNTokenResources patterns; - @JsonCreator public static PNToken of( - @JsonProperty("v") final int v, - @JsonProperty("t") final long t, - @JsonProperty("ttl") final long ttl, - @JsonProperty("res") final PNTokenResources res, - @JsonProperty("pat") final PNTokenResources pat, - @JsonProperty("uuid") final String uuid, - @JsonProperty("meta") final Object meta) { + final int v, + final long t, + final long ttl, + final PNTokenResources res, + final PNTokenResources pat, + final String uuid, + final Object meta) { return new PNToken(v, t, ttl, uuid, meta, res, pat); } @Data - @JsonIgnoreProperties(ignoreUnknown = true) public static class PNTokenResources { @NonNull private final Map channels; @@ -44,10 +38,9 @@ public static class PNTokenResources { @NonNull private final Map uuids; - @JsonCreator - public static PNTokenResources of(@JsonProperty("chan") final Map chan, - @JsonProperty("grp") final Map grp, - @JsonProperty("uuid") final Map uuid) { + public static PNTokenResources of(final Map chan, + final Map grp, + final Map uuid) { return new PNTokenResources(chan, grp, uuid); } @@ -63,7 +56,6 @@ public static class PNResourcePermissions { private final boolean update; private final boolean join; - @JsonCreator public static PNResourcePermissions of(int grant) { return new PNResourcePermissions( (grant & TokenBitmask.READ) != 0, diff --git a/src/test/java/com/pubnub/api/PubNubTest.java b/src/test/java/com/pubnub/api/PubNubTest.java index cf039f361..8a9350716 100644 --- a/src/test/java/com/pubnub/api/PubNubTest.java +++ b/src/test/java/com/pubnub/api/PubNubTest.java @@ -100,7 +100,7 @@ public void getVersionAndTimeStamp() { pubnub = new PubNub(pnConfiguration); String version = pubnub.getVersion(); int timeStamp = pubnub.getTimestamp(); - Assert.assertEquals("6.4.3", version); + Assert.assertEquals("6.4.4", version); Assert.assertTrue(timeStamp > 0); } diff --git a/src/test/java/com/pubnub/api/managers/MapperManagerTest.java b/src/test/java/com/pubnub/api/managers/MapperManagerTest.java new file mode 100644 index 000000000..7d1f62a70 --- /dev/null +++ b/src/test/java/com/pubnub/api/managers/MapperManagerTest.java @@ -0,0 +1,83 @@ +package com.pubnub.api.managers; + +import com.pubnub.api.PubNubException; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.*; + +class MapperManagerTest { + + @Test + void toJson_anonymousList() throws PubNubException { + MapperManager mapperManager = new MapperManager(); + + String expected = "[1,2,3]"; + List anonList = new ArrayList(){{ + add(1); + add(2); + add(3); + }}; + List regularList = new ArrayList(); + regularList.add(1); + regularList.add(2); + regularList.add(3); + + + String json1 = mapperManager.toJson(anonList); + String json2 = mapperManager.toJson(regularList); + + + assertEquals(expected, json1); + assertEquals(expected, json2); + } + + @Test + void toJson_anonymousMap() throws PubNubException { + MapperManager mapperManager = new MapperManager(); + String expected = "{\"city\":\"Toronto\"}"; + + HashMap anonMap = new HashMap() {{ + put("city", "Toronto"); + }}; + + HashMap regularMap = new HashMap(); + regularMap.put("city", "Toronto"); + + + String json1 = mapperManager.toJson(anonMap); + String json2 = mapperManager.toJson(regularMap); + assertEquals(expected, json1); + assertEquals(expected, json2); + } + + @Test + void toJson_anonymousSet() throws PubNubException { + MapperManager mapperManager = new MapperManager(); + + String expected = "[1,2,3]"; + Set anonSet = new HashSet(){{ + add(1); + add(2); + add(3); + }}; + Set regularSet = new HashSet(); + regularSet.add(1); + regularSet.add(2); + regularSet.add(3); + + + String json1 = mapperManager.toJson(anonSet); + String json2 = mapperManager.toJson(regularSet); + + + assertEquals(expected, json1); + assertEquals(expected, json2); + } + +} \ No newline at end of file