From 151c4cbbb7cd45e198d155a39322768e21131fba Mon Sep 17 00:00:00 2001 From: Jonas Dickel Date: Tue, 6 Aug 2024 20:37:02 +0200 Subject: [PATCH] Support for Custom Metaproperties (#126) * fix testcases for windows, remove deprecated initMocks in favour of openMocks, add gitignore to prevent target/ files to be checked into git * replace other initMocks with openMocks calls * implement custom JsonDeserializer to support "property_" prefixed custom metadata --------- Co-authored-by: Jonas Dickel --- .gitignore | 33 ++++++++ .../java/com/bynder/sdk/api/ApiFactory.java | 12 ++- src/main/java/com/bynder/sdk/model/Media.java | 9 +++ .../com/bynder/sdk/util/MediaTypeAdapter.java | 66 ++++++++++++++++ .../com/bynder/sdk/api/ApiFactoryTest.java | 2 +- .../sdk/configuration/ConfigurationTest.java | 2 +- .../HttpConnectionSettingsTest.java | 2 +- .../bynder/sdk/service/BynderClientTest.java | 2 +- .../service/asset/AssetServiceImplTest.java | 2 +- .../collection/CollectionServiceImplTest.java | 2 +- .../service/oauth/OAuthServiceImplTest.java | 2 +- .../bynder/sdk/util/MediaTypeAdapterTest.java | 75 +++++++++++++++++++ .../java/com/bynder/sdk/util/RXUtilsTest.java | 28 ++++--- 13 files changed, 214 insertions(+), 23 deletions(-) create mode 100644 .gitignore create mode 100644 src/main/java/com/bynder/sdk/util/MediaTypeAdapter.java create mode 100644 src/test/java/com/bynder/sdk/util/MediaTypeAdapterTest.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..549e00a2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,33 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/src/main/java/com/bynder/sdk/api/ApiFactory.java b/src/main/java/com/bynder/sdk/api/ApiFactory.java index ab3b2ba5..1d830b9e 100644 --- a/src/main/java/com/bynder/sdk/api/ApiFactory.java +++ b/src/main/java/com/bynder/sdk/api/ApiFactory.java @@ -6,14 +6,21 @@ */ package com.bynder.sdk.api; +import java.io.IOException; +import java.util.Properties; +import java.util.concurrent.TimeUnit; + import com.bynder.sdk.configuration.Configuration; import com.bynder.sdk.configuration.HttpConnectionSettings; import com.bynder.sdk.exception.BynderRuntimeException; +import com.bynder.sdk.model.Media; import com.bynder.sdk.service.BynderClient; import com.bynder.sdk.util.BooleanTypeAdapter; +import com.bynder.sdk.util.MediaTypeAdapter; import com.bynder.sdk.util.StringConverterFactory; import com.bynder.sdk.util.Utils; import com.google.gson.GsonBuilder; + import okhttp3.OkHttpClient; import okhttp3.OkHttpClient.Builder; import okhttp3.Request; @@ -22,10 +29,6 @@ import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory; import retrofit2.converter.gson.GsonConverterFactory; -import java.io.IOException; -import java.util.Properties; -import java.util.concurrent.TimeUnit; - /** * Factory to create API clients. */ @@ -51,6 +54,7 @@ public static BynderApi createBynderClient(final Configuration configuration) { .addConverterFactory(GsonConverterFactory.create( new GsonBuilder() .registerTypeAdapter(Boolean.class, new BooleanTypeAdapter()) + .registerTypeAdapter(Media.class, new MediaTypeAdapter()) .create()) ) .client(createOkHttpClient(configuration)) diff --git a/src/main/java/com/bynder/sdk/model/Media.java b/src/main/java/com/bynder/sdk/model/Media.java index da563cd3..b0cbce6a 100644 --- a/src/main/java/com/bynder/sdk/model/Media.java +++ b/src/main/java/com/bynder/sdk/model/Media.java @@ -120,6 +120,11 @@ public class Media { * versions equal to true. */ private List mediaItems; + /** + * Custom properties not having an PropertyOption. Key is the property name and + * value the value(s) of that property. + */ + private Map> customMetaproperties; public String getId() { return id; @@ -216,4 +221,8 @@ public Map getFocusPoint() { public List getMediaItems() { return mediaItems; } + + public Map> getCustomMetaproperties() { + return customMetaproperties; + } } diff --git a/src/main/java/com/bynder/sdk/util/MediaTypeAdapter.java b/src/main/java/com/bynder/sdk/util/MediaTypeAdapter.java new file mode 100644 index 00000000..d639065a --- /dev/null +++ b/src/main/java/com/bynder/sdk/util/MediaTypeAdapter.java @@ -0,0 +1,66 @@ +package com.bynder.sdk.util; + +import java.lang.reflect.Field; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import com.bynder.sdk.model.Media; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; + +public class MediaTypeAdapter implements JsonDeserializer { + + private static final String PROPERTY_PREFIX = "property_"; + private static final String CUSTOM_METAPROPERTY_FIELDNAME = "customMetaproperties"; + + private final Gson gson; + + public MediaTypeAdapter() { + this.gson = new GsonBuilder().registerTypeAdapter(Boolean.class, new BooleanTypeAdapter()).create(); + } + + @Override + public Media deserialize(final JsonElement json, final Type typeOfT, final JsonDeserializationContext context) + throws JsonParseException { + JsonObject jsonObject = json.getAsJsonObject(); + Media media = gson.fromJson(jsonObject, Media.class); + + Map> metaproperties = new LinkedHashMap<>(); + + for (Map.Entry elementJson : jsonObject.entrySet()) { + if (elementJson.getKey().startsWith(PROPERTY_PREFIX)) { + String propertyName = elementJson.getKey().substring(PROPERTY_PREFIX.length()); + List values = metaproperties.getOrDefault(metaproperties, new ArrayList<>()); + if (elementJson.getValue().isJsonArray()) { + for (JsonElement element : elementJson.getValue().getAsJsonArray()) { + values.add(element.getAsString()); + } + } else { + values.add(elementJson.getValue().getAsString()); + } + metaproperties.put(propertyName, values); + } + } + setMetaproperties(media, metaproperties); + return media; + } + + private void setMetaproperties(Media media, Map> metaproperties) { + try { + Field metapropertiesField = media.getClass().getDeclaredField(CUSTOM_METAPROPERTY_FIELDNAME); + metapropertiesField.setAccessible(true); + metapropertiesField.set(media, metaproperties); + } catch (IllegalArgumentException | IllegalAccessException | NoSuchFieldException | SecurityException e) { + // does not occur unless Media class is changed + } + } + +} diff --git a/src/test/java/com/bynder/sdk/api/ApiFactoryTest.java b/src/test/java/com/bynder/sdk/api/ApiFactoryTest.java index 0f47d0a2..a5aa9348 100644 --- a/src/test/java/com/bynder/sdk/api/ApiFactoryTest.java +++ b/src/test/java/com/bynder/sdk/api/ApiFactoryTest.java @@ -34,7 +34,7 @@ public class ApiFactoryTest { @Before public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); + MockitoAnnotations.openMocks(this); Mockito.when(configuration.getBaseUrl()).thenReturn(new URL(BASE_URL)); Mockito.when(configuration.getHttpConnectionSettings()) .thenReturn(new HttpConnectionSettings()); diff --git a/src/test/java/com/bynder/sdk/configuration/ConfigurationTest.java b/src/test/java/com/bynder/sdk/configuration/ConfigurationTest.java index 1cea35e4..93bb45a3 100644 --- a/src/test/java/com/bynder/sdk/configuration/ConfigurationTest.java +++ b/src/test/java/com/bynder/sdk/configuration/ConfigurationTest.java @@ -35,7 +35,7 @@ public class ConfigurationTest { @Before public void setUp() { - MockitoAnnotations.initMocks(this); + MockitoAnnotations.openMocks(this); } @Test diff --git a/src/test/java/com/bynder/sdk/configuration/HttpConnectionSettingsTest.java b/src/test/java/com/bynder/sdk/configuration/HttpConnectionSettingsTest.java index 5f8c42d9..7cd1ee71 100644 --- a/src/test/java/com/bynder/sdk/configuration/HttpConnectionSettingsTest.java +++ b/src/test/java/com/bynder/sdk/configuration/HttpConnectionSettingsTest.java @@ -40,7 +40,7 @@ public class HttpConnectionSettingsTest { @Before public void setUp() { - MockitoAnnotations.initMocks(this); + MockitoAnnotations.openMocks(this); } @Test diff --git a/src/test/java/com/bynder/sdk/service/BynderClientTest.java b/src/test/java/com/bynder/sdk/service/BynderClientTest.java index cc22f82c..4bd35c86 100644 --- a/src/test/java/com/bynder/sdk/service/BynderClientTest.java +++ b/src/test/java/com/bynder/sdk/service/BynderClientTest.java @@ -34,7 +34,7 @@ public class BynderClientTest { @Before public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); + MockitoAnnotations.openMocks(this); Mockito.when(configuration.getBaseUrl()).thenReturn(new URL(BASE_URL)); Mockito.when(configuration.getHttpConnectionSettings()) .thenReturn(new HttpConnectionSettings()); diff --git a/src/test/java/com/bynder/sdk/service/asset/AssetServiceImplTest.java b/src/test/java/com/bynder/sdk/service/asset/AssetServiceImplTest.java index 527aee2b..07ef1583 100644 --- a/src/test/java/com/bynder/sdk/service/asset/AssetServiceImplTest.java +++ b/src/test/java/com/bynder/sdk/service/asset/AssetServiceImplTest.java @@ -37,7 +37,7 @@ public class AssetServiceImplTest { @Before public void setUp() { - MockitoAnnotations.initMocks(this); + MockitoAnnotations.openMocks(this); assetService = AssetService.Builder.create(bynderApi, queryDecoder); } diff --git a/src/test/java/com/bynder/sdk/service/collection/CollectionServiceImplTest.java b/src/test/java/com/bynder/sdk/service/collection/CollectionServiceImplTest.java index 3d726093..892a956f 100644 --- a/src/test/java/com/bynder/sdk/service/collection/CollectionServiceImplTest.java +++ b/src/test/java/com/bynder/sdk/service/collection/CollectionServiceImplTest.java @@ -38,7 +38,7 @@ public class CollectionServiceImplTest { @Before public void setUp() { - MockitoAnnotations.initMocks(this); + MockitoAnnotations.openMocks(this); collectionService = CollectionService.Builder.create(bynderApi, queryDecoder); } diff --git a/src/test/java/com/bynder/sdk/service/oauth/OAuthServiceImplTest.java b/src/test/java/com/bynder/sdk/service/oauth/OAuthServiceImplTest.java index 52c24130..ef4f09b8 100644 --- a/src/test/java/com/bynder/sdk/service/oauth/OAuthServiceImplTest.java +++ b/src/test/java/com/bynder/sdk/service/oauth/OAuthServiceImplTest.java @@ -61,7 +61,7 @@ public class OAuthServiceImplTest { @Before public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); + MockitoAnnotations.openMocks(this); when(oauthClient.getAccessToken(anyMap())).thenReturn(Observable.just(Response.success(token))); when(configuration.getBaseUrl()).thenReturn(new URL(EXPECTED_BASE_URL)); when(configuration.getOAuthSettings()).thenReturn(oAuthSettings); diff --git a/src/test/java/com/bynder/sdk/util/MediaTypeAdapterTest.java b/src/test/java/com/bynder/sdk/util/MediaTypeAdapterTest.java new file mode 100644 index 00000000..4e353b64 --- /dev/null +++ b/src/test/java/com/bynder/sdk/util/MediaTypeAdapterTest.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2017 Bynder B.V. All rights reserved. + * + * Licensed under the MIT License. See LICENSE file in the project root for full license + * information. + * + * JUnit framework component copyright (c) 2002-2017 JUnit. All Rights Reserved. Licensed under + * Eclipse Public License - v 1.0. You may obtain a copy of the License at + * https://www.eclipse.org/legal/epl-v10.html. + */ +package com.bynder.sdk.util; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.util.Arrays; + +import org.junit.Test; + +import com.bynder.sdk.model.Media; +import com.google.gson.JsonParser; + +/** + * Tests the {@link BooleanTypeAdapter} class methods. + */ +public class MediaTypeAdapterTest { + + + /** + * Given JSON + * + * { + * "id": "FF5DB884-5665-4127-88D0116D8EB379FE", + * "isPublic": 0, + * "property_Articlenumber": "16773", + * "property_Language": [ + * "Neutral" + * ] + * } + * + */ + private final String givenApiResponse = "{ \"id\": \"FF5DB884-5665-4127-88D0116D8EB379FE\", \"isPublic\": 0, \"property_Articlenumber\": \"16773\", \"property_Language\": [ \"Neutral\" ]}"; + + + /** + * Tests that + * {@link MediaTypeAdapter#deserialize(com.google.gson.JsonElement, java.lang.reflect.Type, com.google.gson.JsonDeserializationContext)} + * correctly converts all "property_" prefixed json fields into a custom Map> when deserializing the Json response returned by the + * API. + */ + @Test + public void deserializeWithMediaTypeAdapter() { + MediaTypeAdapter mediaTypeAdapter = new MediaTypeAdapter(); + + Media actualMedia = mediaTypeAdapter.deserialize(JsonParser.parseString(givenApiResponse), null, null); + + // common default field like ID + assertEquals("FF5DB884-5665-4127-88D0116D8EB379FE", actualMedia.getId()); + + // boolean using BooleanTypeAdapter inside MediaTypeAdapter + assertEquals(Boolean.FALSE, actualMedia.isPublic()); + + // new custom "property_" which is returned as string from API# + + assertNotNull(actualMedia.getCustomMetaproperties()); + assertTrue(actualMedia.getCustomMetaproperties().containsKey("Articlenumber")); + assertEquals(Arrays.asList("16773"), actualMedia.getCustomMetaproperties().get("Articlenumber")); + + // new custom "property_" which is returned as array from API + assertNotNull(actualMedia.getCustomMetaproperties()); + assertTrue(actualMedia.getCustomMetaproperties().containsKey("Language")); + assertEquals(Arrays.asList("Neutral"), actualMedia.getCustomMetaproperties().get("Language")); + } +} diff --git a/src/test/java/com/bynder/sdk/util/RXUtilsTest.java b/src/test/java/com/bynder/sdk/util/RXUtilsTest.java index d54d84d2..ad299404 100644 --- a/src/test/java/com/bynder/sdk/util/RXUtilsTest.java +++ b/src/test/java/com/bynder/sdk/util/RXUtilsTest.java @@ -1,23 +1,27 @@ package com.bynder.sdk.util; -import com.bynder.sdk.exception.HttpResponseException; -import io.reactivex.Observable; -import okhttp3.MediaType; -import okhttp3.ResponseBody; -import org.junit.Before; -import org.junit.Test; -import retrofit2.Response; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; import java.io.FileInputStream; import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; import java.nio.file.Files; import java.nio.file.Paths; import java.util.Arrays; import java.util.Iterator; import java.util.List; -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; +import org.junit.Before; +import org.junit.Test; + +import com.bynder.sdk.exception.HttpResponseException; + +import io.reactivex.Observable; +import okhttp3.MediaType; +import okhttp3.ResponseBody; +import retrofit2.Response; public class RXUtilsTest { @@ -93,10 +97,10 @@ public void readFileChunks() throws IOException { } @Test - public void readFile() throws IOException { - String path = ClassLoader.getSystemResource("config.properties").getPath(); + public void readFile() throws IOException, URISyntaxException { + URI path = ClassLoader.getSystemResource("config.properties").toURI(); byte[] expected = Files.readAllBytes(Paths.get(path)); - byte[] actual = RXUtils.readFile(path).blockingGet(); + byte[] actual = RXUtils.readFile(Paths.get(path).toString()).blockingGet(); assertArrayEquals(expected, actual); }