From 9e897d1c97a6f9e71202f61f3eb85bbb632e6f32 Mon Sep 17 00:00:00 2001 From: Sina Madani Date: Tue, 9 May 2023 14:38:08 +0100 Subject: [PATCH 1/3] Prepare v4.10.0 release (#240) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Minor fixes in build.gradle * Added support for MultiBroadcastTag and MultiArchiveTag (#226) * Added multiArchiveTag * Added multiBroadcastTag * Updated docs * Removed getMulti*Tag from responses * Revert "Removed getMulti*Tag from responses" This reverts commit 6e7f123c6d1ad402754dda09ca1b7a9c81436de6. * Docs edits re multiArchive/BroadcastTag. Co-authored-by: Jeff Swartz * Add Experience Composer API (#227) * Added startRender * Added listRenders * Deserialize listRenders response to native List * Added stopRender & getRender * Use enum for Render status * Improved RenderProperties * Experience composer docs edits ... And other docs corrections Co-authored-by: Jeff Swartz * Bump dependency versions * Improved release process. bump2version is now manual, but release to Nexus is fully automated. * Ensure version is passed in bumpversion.sh * Bumped dependencies (including WireMock) * Bump version: v4.8.0 → v4.8.1 * Update copyright year * Use wiremock-jre8 * Boost coverage * Boost coverage * Disable codecov temporarily * Revert "Disable codecov temporarily" This reverts commit 613bff0794ff299d1165952a57da903a700136ec. * Add Audio Streamer (lite) API endpoint (#215) * Added Audio Streamer (lite) endpoint * Don't include headers or streams if empty in connect request * Renamed Connect for clarity * Addressed PR comments * Fixed failing test * URI in AudioStreamerConnectionProperties constructor * Docs edits * Bumped dependencies (notably WireMock to 2.x) * Bumped dependencies * Bump dependency versions (#233) Improved release process, bumped dependency versions (including WireMock), slightly improved test coverage, updated copyright year, merged main, ready for release v4.8.1. * Renamed to Audio Connector * Bumped dependencies * Added audio connector to README * Minor docs edits --------- Co-authored-by: Jeff Swartz * Bump version: v4.8.1 → v4.9.0 * Minor docs edit * Enable custom User-Agent (#238) * Add end-to-end encryption support (#237) * Added e2ee support * Added validation for SessionProperties * Only include e2ee if true * Update SessionProperties javadoc Co-authored-by: Jeff Swartz * Minor docs edit --------- Co-authored-by: Jeff Swartz * Bump jackson version * Bump version: v4.9.0 → v4.10.0 --------- Co-authored-by: Jeff Swartz --- .bumpversion.cfg | 2 +- README.md | 4 +- build.gradle | 4 +- src/main/java/com/opentok/OpenTok.java | 23 +++- src/main/java/com/opentok/Session.java | 7 +- .../java/com/opentok/SessionProperties.java | 75 +++++++++---- .../opentok/constants/DefaultUserAgent.java | 13 +++ .../java/com/opentok/constants/Version.java | 2 +- .../java/com/opentok/util/HttpClient.java | 32 +++--- .../java/com/opentok/test/OpenTokTest.java | 102 ++++++++++++++++-- 10 files changed, 206 insertions(+), 58 deletions(-) create mode 100644 src/main/java/com/opentok/constants/DefaultUserAgent.java diff --git a/.bumpversion.cfg b/.bumpversion.cfg index f663577e..54ae869e 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,7 +1,7 @@ [bumpversion] commit = True tag = False -current_version = v4.9.0 +current_version = v4.10.0 parse = (?P\d+)\.(?P\d+)\.(?P\d+)(\-(?P[a-z]+)(?P\d+))? serialize = {major}.{minor}.{patch}-{release}{build} diff --git a/README.md b/README.md index 84cf0e02..0bdbda12 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ When you use Maven as your build tool, you can manage dependencies in the `pom.x com.tokbox opentok-server-sdk - 4.9.0 + 4.10.0 ``` @@ -44,7 +44,7 @@ When you use Gradle as your build tool, you can manage dependencies in the `buil ```groovy dependencies { - compile group: 'com.tokbox', name: 'opentok-server-sdk', version: '4.9.0' + compile group: 'com.tokbox', name: 'opentok-server-sdk', version: '4.10.0' } ``` diff --git a/build.gradle b/build.gradle index ebe8f69d..c83960e5 100644 --- a/build.gradle +++ b/build.gradle @@ -11,7 +11,7 @@ plugins { group = 'com.tokbox' archivesBaseName = 'opentok-server-sdk' -version = '4.9.0' +version = '4.10.0' sourceCompatibility = "1.8" targetCompatibility = "1.8" @@ -27,7 +27,7 @@ dependencies { implementation 'commons-lang:commons-lang:2.6' implementation 'commons-validator:commons-validator:1.7' implementation 'org.asynchttpclient:async-http-client:2.12.3' - implementation 'com.fasterxml.jackson.core:jackson-databind:2.14.2' + implementation 'com.fasterxml.jackson.core:jackson-databind:2.15.0' implementation 'commons-codec:commons-codec:1.15' implementation 'org.bitbucket.b_c:jose4j:0.9.3' implementation 'io.netty:netty-handler:4.1.89.Final' diff --git a/src/main/java/com/opentok/OpenTok.java b/src/main/java/com/opentok/OpenTok.java index 95a1b286..da35665d 100644 --- a/src/main/java/com/opentok/OpenTok.java +++ b/src/main/java/com/opentok/OpenTok.java @@ -11,6 +11,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectReader; +import com.opentok.constants.DefaultUserAgent; import com.opentok.exception.InvalidArgumentException; import com.opentok.exception.OpenTokException; import com.opentok.exception.RequestException; @@ -63,9 +64,7 @@ public class OpenTok { * @param apiSecret Your OpenTok API secret. (See your Vonage Video API account page.) */ public OpenTok(int apiKey, String apiSecret) { - this.apiKey = apiKey; - this.apiSecret = apiSecret.trim(); - this.client = new HttpClient.Builder(apiKey, apiSecret).build(); + this(apiKey, apiSecret, new HttpClient.Builder(apiKey, apiSecret).build()); } private OpenTok(int apiKey, String apiSecret, HttpClient httpClient) { @@ -243,7 +242,7 @@ public String generateToken(String sessionId) throws OpenTokException { */ public Session createSession(SessionProperties properties) throws OpenTokException { final SessionProperties _properties = properties != null ? properties : new SessionProperties.Builder().build(); - final Map> params = _properties.toMap(); + final Map> params = _properties.toMap(); final String response = client.createSession(params); try { @@ -991,6 +990,7 @@ public static class Builder { private int apiKey; private String apiSecret; private String apiUrl; + private String appendUserAgent; private Proxy proxy; private ProxyAuthScheme proxyAuthScheme; private String principal; @@ -1045,6 +1045,18 @@ public Builder proxy(Proxy proxy, ProxyAuthScheme proxyAuthScheme, String princi return this; } + /** + * Append a custom string to the client's User-Agent. This is to enable tracking for custom integrations. + * + * @param appendUserAgent The string to append to the user agent. + * + * @return This Builder with the additional user agent string. + */ + public Builder appendToUserAgent(String appendUserAgent) { + this.appendUserAgent = appendUserAgent; + return this; + } + /** * Builds the OpenTok object with the settings provided to this * Builder object. @@ -1063,6 +1075,9 @@ public OpenTok build() { if (requestTimeout != 0) { clientBuilder.requestTimeoutMS(requestTimeout); } + if (appendUserAgent != null && !appendUserAgent.trim().isEmpty()) { + clientBuilder.userAgent(DefaultUserAgent.DEFAULT_USER_AGENT+" "+appendUserAgent); + } return new OpenTok(apiKey, apiSecret, clientBuilder.build()); } diff --git a/src/main/java/com/opentok/Session.java b/src/main/java/com/opentok/Session.java index 28b14ebd..f78b4c5d 100644 --- a/src/main/java/com/opentok/Session.java +++ b/src/main/java/com/opentok/Session.java @@ -27,17 +27,13 @@ * to get the session ID. */ public class Session { - private String sessionId; private int apiKey; private String apiSecret; private SessionProperties properties; protected Session(String sessionId, int apiKey, String apiSecret) { - this.sessionId = sessionId; - this.apiKey = apiKey; - this.apiSecret = apiSecret; - this.properties = new SessionProperties.Builder().build(); + this(sessionId, apiKey, apiSecret, new SessionProperties.Builder().build()); } protected Session(String sessionId, int apiKey, String apiSecret, SessionProperties properties) { @@ -83,7 +79,6 @@ public SessionProperties getProperties() { * @see #generateToken(TokenOptions tokenOptions) */ public String generateToken() throws OpenTokException { - // NOTE: maybe there should be a static object for the defaultTokenOptions? return this.generateToken(new TokenOptions.Builder().build()); } diff --git a/src/main/java/com/opentok/SessionProperties.java b/src/main/java/com/opentok/SessionProperties.java index f7e6ad9b..55eac725 100644 --- a/src/main/java/com/opentok/SessionProperties.java +++ b/src/main/java/com/opentok/SessionProperties.java @@ -10,10 +10,7 @@ import com.opentok.exception.InvalidArgumentException; import org.apache.commons.validator.routines.InetAddressValidator; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; +import java.util.*; /** @@ -23,16 +20,16 @@ * @see OpenTok#createSession(com.opentok.SessionProperties properties) */ public class SessionProperties { - - private String location; private MediaMode mediaMode; private ArchiveMode archiveMode; + private boolean e2ee; private SessionProperties(Builder builder) { this.location = builder.location; this.mediaMode = builder.mediaMode; this.archiveMode = builder.archiveMode; + this.e2ee = builder.e2ee; } /** @@ -41,10 +38,10 @@ private SessionProperties(Builder builder) { * @see SessionProperties */ public static class Builder { - private String location = null; + private String location; private MediaMode mediaMode = MediaMode.RELAYED; private ArchiveMode archiveMode = ArchiveMode.MANUAL; - + private boolean e2ee = false; /** * Call this method to set an IP address that the OpenTok servers will use to @@ -108,7 +105,7 @@ public Builder mediaMode(MediaMode mediaMode) { /** * Call this method to determine whether the session will be automatically archived (ArchiveMode.ALWAYS) * or not (ArchiveMode.MANUAL). - * + *

* Using an always archived session also requires the routed media mode (MediaMode.ROUTED). * * @param archiveMode The Archive mode. @@ -120,19 +117,43 @@ public Builder archiveMode(ArchiveMode archiveMode) { return this; } + /** + * Enables end-to-end encryption for a routed session. + * You must also set {@link #mediaMode(MediaMode)} to {@linkplain MediaMode#ROUTED} when + * calling this method. + * + * @return The SessionProperties.Builder object with the e2ee property set to {@code true}. + */ + public Builder endToEndEncryption() { + this.e2ee = true; + return this; + } + /** * Builds the SessionProperties object. * * @return The SessionProperties object. */ public SessionProperties build() { - // Would throw in this case, but would introduce a backwards incompatible change. - //if (this.archiveMode == ArchiveMode.ALWAYS && this.mediaMode != MediaMode.ROUTED) { - // throw new InvalidArgumentException("A session with always archive mode must also have the routed media mode."); - //} + if (this.archiveMode == ArchiveMode.ALWAYS && this.mediaMode != MediaMode.ROUTED) { + throw new IllegalStateException( + "A session with ALWAYS archive mode must also have the ROUTED media mode." + ); + } + if (e2ee && mediaMode != MediaMode.ROUTED) { + throw new IllegalStateException( + "A session with RELAYED media mode cannot have end-to-end encryption enabled." + ); + } + if (e2ee && archiveMode == ArchiveMode.ALWAYS) { + throw new IllegalStateException( + "A session with ALWAYS archive mode cannot have end-to-end encryption enabled." + ); + } return new SessionProperties(this); } } + /** * The location hint IP address. See {@link SessionProperties.Builder#location(String location)}. */ @@ -158,25 +179,43 @@ public ArchiveMode archiveMode() { return archiveMode; } + /** + * Defines whether the session will use + * end-to-end encryption. + * See {@link com.opentok.SessionProperties.Builder#endToEndEncryption()}. + * + * + * @return {@code true} if end-to-end encryption is enabled, {@code false} otherwise. + */ + public boolean isEndToEndEncrypted() { + return e2ee; + } + /** * Returns the session properties as a Map. */ - public Map> toMap() { - Map> params = new HashMap<>(); + public Map> toMap() { + Map> params = new HashMap<>(); if (null != location) { - ArrayList valueList = new ArrayList<>(); + ArrayList valueList = new ArrayList<>(1); valueList.add(location); params.put("location", valueList); } - ArrayList mediaModeValueList = new ArrayList<>(); + ArrayList mediaModeValueList = new ArrayList<>(1); mediaModeValueList.add(mediaMode.toString()); params.put("p2p.preference", mediaModeValueList); - ArrayList archiveModeValueList = new ArrayList<>(); + ArrayList archiveModeValueList = new ArrayList<>(1); archiveModeValueList.add(archiveMode.toString()); params.put("archiveMode", archiveModeValueList); + if (e2ee) { + ArrayList e2eeValueList = new ArrayList<>(1); + e2eeValueList.add("" + e2ee); + params.put("e2ee", e2eeValueList); + } + return params; } diff --git a/src/main/java/com/opentok/constants/DefaultUserAgent.java b/src/main/java/com/opentok/constants/DefaultUserAgent.java new file mode 100644 index 00000000..51b1b7f4 --- /dev/null +++ b/src/main/java/com/opentok/constants/DefaultUserAgent.java @@ -0,0 +1,13 @@ +/** + * OpenTok Java SDK + * Copyright (C) 2023 Vonage. + * http://www.tokbox.com + * + * Licensed under The MIT License (MIT). See LICENSE file for more information. + */ +package com.opentok.constants; + +public class DefaultUserAgent { + public static final String DEFAULT_USER_AGENT = + "Opentok-Java-SDK/"+Version.VERSION+" JRE/"+System.getProperty("java.version"); +} diff --git a/src/main/java/com/opentok/constants/Version.java b/src/main/java/com/opentok/constants/Version.java index f89f5457..d0ff3692 100644 --- a/src/main/java/com/opentok/constants/Version.java +++ b/src/main/java/com/opentok/constants/Version.java @@ -8,5 +8,5 @@ package com.opentok.constants; public class Version { - public static final String VERSION = "4.9.0"; + public static final String VERSION = "4.10.0"; } diff --git a/src/main/java/com/opentok/util/HttpClient.java b/src/main/java/com/opentok/util/HttpClient.java index d726b3fe..bd0bbc0a 100644 --- a/src/main/java/com/opentok/util/HttpClient.java +++ b/src/main/java/com/opentok/util/HttpClient.java @@ -16,6 +16,7 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import com.opentok.*; import com.opentok.constants.DefaultApiUrl; +import com.opentok.constants.DefaultUserAgent; import com.opentok.constants.Version; import com.opentok.exception.InvalidArgumentException; import com.opentok.exception.OpenTokException; @@ -38,7 +39,6 @@ import java.util.concurrent.Future; public class HttpClient extends DefaultAsyncHttpClient { - private final String apiUrl; private final int apiKey; @@ -48,17 +48,9 @@ private HttpClient(Builder builder) { this.apiUrl = builder.apiUrl; } - public String createSession(Map> params) throws RequestException { - Map> paramsWithList = null; - if (params != null) { - paramsWithList = new HashMap<>(); - for (Entry> entry : params.entrySet()) { - paramsWithList.put(entry.getKey(), new ArrayList<>(entry.getValue())); - } - } - + public String createSession(Map> params) throws RequestException { Future request = this.preparePost(this.apiUrl + "/session/create") - .setFormParams(paramsWithList) + .setFormParams(params) .setHeader("Accept", "application/json") // XML version is deprecated .execute(); @@ -1303,6 +1295,7 @@ public static class Builder { private String principal; private String password; private String apiUrl; + private String userAgent = DefaultUserAgent.DEFAULT_USER_AGENT; private AsyncHttpClientConfig config; private int requestTimeoutMS; @@ -1340,18 +1333,29 @@ public Builder requestTimeoutMS(int requestTimeoutMS) { return this; } + /** + * Sets the user agent to a custom value. + * + * @param userAgent The user agent. + * + * @return This Builder with user agent string. + */ + public Builder userAgent(String userAgent) { + this.userAgent = userAgent; + return this; + } + public HttpClient build() { DefaultAsyncHttpClientConfig.Builder configBuilder = new DefaultAsyncHttpClientConfig.Builder() - .setUserAgent("Opentok-Java-SDK/" + Version.VERSION + " JRE/" + System.getProperty("java.version")) + .setUserAgent(userAgent) .addRequestFilter(new TokenAuthRequestFilter(apiKey, apiSecret)); + if (apiUrl == null) { apiUrl = DefaultApiUrl.DEFAULT_API_URI; } - if (proxy != null) { configBuilder.setProxyServer(createProxyServer(proxy, proxyAuthScheme, principal, password)); } - if (requestTimeoutMS != 0) { configBuilder.setRequestTimeout(requestTimeoutMS); } diff --git a/src/test/java/com/opentok/test/OpenTokTest.java b/src/test/java/com/opentok/test/OpenTokTest.java index 9ee804e4..7cc7c1db 100644 --- a/src/test/java/com/opentok/test/OpenTokTest.java +++ b/src/test/java/com/opentok/test/OpenTokTest.java @@ -15,10 +15,12 @@ import com.github.tomakehurst.wiremock.junit.WireMockRule; import com.opentok.*; import com.opentok.Archive.OutputMode; +import com.opentok.constants.DefaultUserAgent; import com.opentok.exception.InvalidArgumentException; import com.opentok.exception.OpenTokException; import com.opentok.exception.RequestException; import org.apache.commons.lang.StringUtils; +import org.checkerframework.common.returnsreceiver.qual.This; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -68,12 +70,32 @@ public void setUp() throws OpenTokException { sdk = new OpenTok.Builder(apiKey, apiSecret).apiUrl(apiUrl).build(); } + @Test + public void testUserAgent() throws Exception { + stubFor(post(anyUrl()) + .withHeader("User-Agent", equalTo(DefaultUserAgent.DEFAULT_USER_AGENT)) + .willReturn(aResponse().withStatus(200)) + ); + sdk.disableForceMute("SESSION_ID"); + verify(postRequestedFor(anyUrl())); + + sdk = new OpenTok.Builder(apiKey, apiSecret).apiUrl(apiUrl).appendToUserAgent("Test_UA").build(); + stubFor(post(anyUrl()) + .withHeader("User-Agent", equalTo(DefaultUserAgent.DEFAULT_USER_AGENT+" Test_UA")) + .willReturn(aResponse().withStatus(200)) + ); + sdk.disableForceMute("SESSION_ID"); + verify(postRequestedFor(anyUrl())); + WireMock.reset(); + } + /** * Test that a request throws exception if request exceeds configured timeout */ @Test public void testConfigureRequestTimeout() { assertThrows(RequestException.class, () -> { + sdk.close(); sdk = new OpenTok.Builder(apiKey, apiSecret).apiUrl(apiUrl).requestTimeout(6).build(); String sessionId = "SESSIONID"; @@ -94,7 +116,6 @@ public void testConfigureRequestTimeout() { assertNull(createdSession.getMediaServerURL()); assertNull(createdSession.getProjectId()); assertNull(createdSession.getPartnerId()); - } @Test @@ -220,6 +241,7 @@ public void testCreateDefaultSession() throws OpenTokException { Session session = sdk.createSession(); assertNotNull(session); + assertFalse(session.getProperties().isEndToEndEncrypted()); assertEquals(apiKey, session.getApiKey()); assertEquals(sessionId, session.getSessionId()); assertEquals(MediaMode.RELAYED, session.getProperties().mediaMode()); @@ -252,6 +274,8 @@ public void testCreateRoutedSession() throws OpenTokException { Session session = sdk.createSession(properties); assertNotNull(session); + assertEquals(properties, session.getProperties()); + assertFalse(session.getProperties().isEndToEndEncrypted()); assertEquals(apiKey, session.getApiKey()); assertEquals(sessionId, session.getSessionId()); assertEquals(MediaMode.ROUTED, session.getProperties().mediaMode()); @@ -284,6 +308,8 @@ public void testCreateLocationHintSession() throws OpenTokException { Session session = sdk.createSession(properties); assertNotNull(session); + assertEquals(properties, session.getProperties()); + assertFalse(session.getProperties().isEndToEndEncrypted()); assertEquals(apiKey, session.getApiKey()); assertEquals(sessionId, session.getSessionId()); assertEquals(MediaMode.RELAYED, session.getProperties().mediaMode()); @@ -297,6 +323,41 @@ public void testCreateLocationHintSession() throws OpenTokException { Helpers.verifyUserAgent(); } + @Test + public void testCreateEncryptedSession() throws OpenTokException { + String sessionId = "SESSION1D"; + stubFor(post(urlEqualTo(SESSION_CREATE)) + .willReturn(aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody("[{\"session_id\":\"" + sessionId + "\",\"project_id\":\"00000000\"," + + "\"partner_id\":\"123456\"," + + "\"create_dt\":\"Mon Mar 17 00:41:31 PDT 2014\"," + + "\"media_server_url\":\"\"}]"))); + + SessionProperties properties = new SessionProperties.Builder() + .endToEndEncryption() + .mediaMode(MediaMode.ROUTED) + .build(); + Session session = sdk.createSession(properties); + + assertNotNull(session); + assertEquals(properties, session.getProperties()); + assertTrue(session.getProperties().isEndToEndEncrypted()); + assertEquals(apiKey, session.getApiKey()); + assertEquals(sessionId, session.getSessionId()); + assertEquals(MediaMode.ROUTED, session.getProperties().mediaMode()); + assertEquals(ArchiveMode.MANUAL, session.getProperties().archiveMode()); + assertNull(session.getProperties().getLocation()); + + verify(postRequestedFor(urlMatching(SESSION_CREATE)) + // NOTE: this is a pretty bad way to verify, ideally we can decode the body and then query the object + .withRequestBody(matching(".*e2ee=true.*"))); + assertTrue(Helpers.verifyTokenAuth(apiKey, apiSecret, + findAll(postRequestedFor(urlMatching(SESSION_CREATE))))); + Helpers.verifyUserAgent(); + } + @Test public void testCreateAlwaysArchivedSession() throws OpenTokException { String sessionId = "SESSIONID"; @@ -311,6 +372,7 @@ public void testCreateAlwaysArchivedSession() throws OpenTokException { SessionProperties properties = new SessionProperties.Builder() .archiveMode(ArchiveMode.ALWAYS) + .mediaMode(MediaMode.ROUTED) .build(); Session session = sdk.createSession(properties); @@ -335,16 +397,36 @@ public void testCreateBadSession() throws OpenTokException { .build(); } -// This is not part of the API because it would introduce a backwards incompatible change. -// @Test(expected = InvalidArgumentException.class) -// public void testCreateInvalidAlwaysArchivedAndRelayedSession() throws OpenTokException { -// SessionProperties properties = new SessionProperties.Builder() -// .mediaMode(MediaMode.RELAYED) -// .archiveMode(ArchiveMode.ALWAYS) -// .build(); -// } + @Test(expected = IllegalStateException.class) + public void testCreateInvalidAlwaysArchivedAndRelayedSession() { + new SessionProperties.Builder() + .mediaMode(MediaMode.RELAYED) + .archiveMode(ArchiveMode.ALWAYS) + .build(); + } + + @Test(expected = IllegalStateException.class) + public void testCreateInvalidAlwaysArchivedAndE2eeSession() { + new SessionProperties.Builder() + .mediaMode(MediaMode.ROUTED) + .endToEndEncryption() + .archiveMode(ArchiveMode.ALWAYS) + .build(); + } - // TODO: test session creation conditions that result in errors + @Test(expected = IllegalStateException.class) + public void testCreateInvalidRelayedMediaAndE2eeSession() { + new SessionProperties.Builder() + .archiveMode(ArchiveMode.MANUAL) + .endToEndEncryption() + .mediaMode(MediaMode.RELAYED) + .build(); + } + + @Test(expected = IllegalStateException.class) + public void testCreateInvalidE2eeSessionDefault() { + new SessionProperties.Builder().endToEndEncryption().build(); + } @Test public void testTokenDefault() throws From abbfdf75008c194bdcb52845fb78b0686eab2236 Mon Sep 17 00:00:00 2001 From: Sina Madani Date: Tue, 23 May 2023 14:28:39 +0100 Subject: [PATCH 2/3] Added auto archive properties to session --- src/main/java/com/opentok/Resolution.java | 72 +++++++++++ .../java/com/opentok/SessionProperties.java | 114 +++++++++++++++--- .../java/com/opentok/test/OpenTokTest.java | 39 +++++- 3 files changed, 203 insertions(+), 22 deletions(-) create mode 100644 src/main/java/com/opentok/Resolution.java diff --git a/src/main/java/com/opentok/Resolution.java b/src/main/java/com/opentok/Resolution.java new file mode 100644 index 00000000..16ac227c --- /dev/null +++ b/src/main/java/com/opentok/Resolution.java @@ -0,0 +1,72 @@ +/** + * OpenTok Java SDK + * Copyright (C) 2023 Vonage. + * http://www.tokbox.com + * + * Licensed under The MIT License (MIT). See LICENSE file for more information. + */ +package com.opentok; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; +import java.util.Arrays; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * Defines valid video resolutions for an archive. + */ +public enum Resolution { + /** + * 480p landscape (640x480) + */ + SD_LANDSCAPE("640x480"), + + /** + * 480p portrait (480x640) + */ + SD_PORTRAIT("480x640"), + + /** + * 720p landscape (1280x720) + */ + HD_LANDSCAPE("1280x720"), + + /** + * 720p portrait (720x1280) + */ + HD_PORTRAIT("720x1280"), + + /** + * 1080p landscape (1920x1080) + */ + FHD_LANDSCAPE("1920x1080"), + + /** + * 1080p portrait (1080x1920) + */ + FHD_PORTRAIT("1080x1920"); + + private static final Map RESOLUTION_INDEX = + Arrays.stream(Resolution.values()).collect(Collectors.toMap( + Resolution::toString, Function.identity() + )); + + private final String value; + + Resolution(String value) { + this.value = value; + } + + @JsonValue + @Override + public String toString() { + return value; + } + + @JsonCreator + public static Resolution fromString(String resolution) { + return RESOLUTION_INDEX.getOrDefault(resolution, null); + } +} diff --git a/src/main/java/com/opentok/SessionProperties.java b/src/main/java/com/opentok/SessionProperties.java index 55eac725..267ecfbc 100644 --- a/src/main/java/com/opentok/SessionProperties.java +++ b/src/main/java/com/opentok/SessionProperties.java @@ -20,16 +20,19 @@ * @see OpenTok#createSession(com.opentok.SessionProperties properties) */ public class SessionProperties { - private String location; + private String location, archiveName; private MediaMode mediaMode; private ArchiveMode archiveMode; + private Resolution archiveResolution; private boolean e2ee; private SessionProperties(Builder builder) { - this.location = builder.location; - this.mediaMode = builder.mediaMode; - this.archiveMode = builder.archiveMode; - this.e2ee = builder.e2ee; + location = builder.location; + mediaMode = builder.mediaMode; + archiveMode = builder.archiveMode; + e2ee = builder.e2ee; + archiveName = builder.archiveName; + archiveResolution = builder.archiveResolution; } /** @@ -38,9 +41,10 @@ private SessionProperties(Builder builder) { * @see SessionProperties */ public static class Builder { - private String location; + private String location, archiveName; private MediaMode mediaMode = MediaMode.RELAYED; private ArchiveMode archiveMode = ArchiveMode.MANUAL; + private Resolution archiveResolution; private boolean e2ee = false; /** @@ -117,6 +121,32 @@ public Builder archiveMode(ArchiveMode archiveMode) { return this; } + /** + * Indicates the archive resolution for all the archives in auto archived session. A session that begins with + * archive mode {@link ArchiveMode#ALWAYS} will use this resolution for all archives of that session. + * + * @param archiveResolution The auto archive resolution as an enum. + * + * @return The SessionProperties.Builder object with the archive resolution setting. + */ + public Builder archiveResolution(Resolution archiveResolution) { + this.archiveResolution = archiveResolution; + return this; + } + + /** + * Indicates the archive name for all the archives in auto archived session. A session that begins with + * archive mode {@link ArchiveMode#ALWAYS} will use this archive name for all archives of that session. + * + * @param archiveName The archive name, maximum 80 characters in length. + * + * @return The SessionProperties.Builder object with the archive name setting. + */ + public Builder archiveName(String archiveName) { + this.archiveName = archiveName; + return this; + } + /** * Enables end-to-end encryption for a routed session. * You must also set {@link #mediaMode(MediaMode)} to {@linkplain MediaMode#ROUTED} when @@ -135,7 +165,7 @@ public Builder endToEndEncryption() { * @return The SessionProperties object. */ public SessionProperties build() { - if (this.archiveMode == ArchiveMode.ALWAYS && this.mediaMode != MediaMode.ROUTED) { + if (archiveMode == ArchiveMode.ALWAYS && mediaMode != MediaMode.ROUTED) { throw new IllegalStateException( "A session with ALWAYS archive mode must also have the ROUTED media mode." ); @@ -150,6 +180,17 @@ public SessionProperties build() { "A session with ALWAYS archive mode cannot have end-to-end encryption enabled." ); } + if (archiveMode == ArchiveMode.MANUAL) { + if (archiveResolution != null) { + throw new IllegalStateException("Resolution cannot be set for manual archives."); + } + if (archiveName != null) { + throw new IllegalStateException("Name cannot be set for manual archives."); + } + } + if (archiveName != null && (archiveName.trim().length() < 1 || archiveName.length() > 80)) { + throw new IllegalArgumentException("Archive name must be between 1 and 80 characters."); + } return new SessionProperties(this); } } @@ -179,11 +220,30 @@ public ArchiveMode archiveMode() { return archiveMode; } + /** + * Indicates the archive resolution for all the archives in auto archived session. A session that begins with + * archive mode {@link ArchiveMode#ALWAYS} will use this resolution for all archives of that session. + * + * @return The archive name, or {@code null} if not set (the default). + */ + public String archiveName() { + return archiveName; + } + + /** + * Indicates the archive resolution for all the archives in auto archived session. A session that begins with + * archive mode {@link ArchiveMode#ALWAYS} will use this resolution for all archives of that session. + * + * @return The archive resolution enum, or {@code null} if not set (the default). + */ + public Resolution archiveResolution() { + return archiveResolution; + } + /** * Defines whether the session will use * end-to-end encryption. * See {@link com.opentok.SessionProperties.Builder#endToEndEncryption()}. - * * * @return {@code true} if end-to-end encryption is enabled, {@code false} otherwise. */ @@ -192,27 +252,41 @@ public boolean isEndToEndEncrypted() { } /** - * Returns the session properties as a Map. + * Serializes the properties for making a request. + * + * @return The session properties as a Map. */ public Map> toMap() { Map> params = new HashMap<>(); - if (null != location) { + + if (location != null) { ArrayList valueList = new ArrayList<>(1); valueList.add(location); params.put("location", valueList); } - - ArrayList mediaModeValueList = new ArrayList<>(1); - mediaModeValueList.add(mediaMode.toString()); - params.put("p2p.preference", mediaModeValueList); - - ArrayList archiveModeValueList = new ArrayList<>(1); - archiveModeValueList.add(archiveMode.toString()); - params.put("archiveMode", archiveModeValueList); - + if (mediaMode != null) { + ArrayList mediaModeValueList = new ArrayList<>(1); + mediaModeValueList.add(mediaMode.toString()); + params.put("p2p.preference", mediaModeValueList); + } + if (archiveMode != null) { + ArrayList archiveModeValueList = new ArrayList<>(1); + archiveModeValueList.add(archiveMode.toString()); + params.put("archiveMode", archiveModeValueList); + } + if (archiveResolution != null) { + ArrayList archiveResolutionValueList = new ArrayList<>(1); + archiveResolutionValueList.add(archiveResolution.toString()); + params.put("archiveResolution", archiveResolutionValueList); + } + if (archiveName != null) { + ArrayList archiveNameValueList = new ArrayList<>(1); + archiveNameValueList.add(archiveName); + params.put("archiveName", archiveNameValueList); + } if (e2ee) { ArrayList e2eeValueList = new ArrayList<>(1); - e2eeValueList.add("" + e2ee); + e2eeValueList.add(String.valueOf(e2ee)); params.put("e2ee", e2eeValueList); } diff --git a/src/test/java/com/opentok/test/OpenTokTest.java b/src/test/java/com/opentok/test/OpenTokTest.java index 7cc7c1db..4414f1a2 100644 --- a/src/test/java/com/opentok/test/OpenTokTest.java +++ b/src/test/java/com/opentok/test/OpenTokTest.java @@ -11,6 +11,7 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import com.github.tomakehurst.wiremock.WireMockServer; import com.github.tomakehurst.wiremock.client.WireMock; +import static com.github.tomakehurst.wiremock.client.WireMock.matching; import com.github.tomakehurst.wiremock.core.WireMockConfiguration; import com.github.tomakehurst.wiremock.junit.WireMockRule; import com.opentok.*; @@ -373,6 +374,8 @@ public void testCreateAlwaysArchivedSession() throws OpenTokException { SessionProperties properties = new SessionProperties.Builder() .archiveMode(ArchiveMode.ALWAYS) .mediaMode(MediaMode.ROUTED) + .archiveResolution(Resolution.HD_PORTRAIT) + .archiveName("720pTest") .build(); Session session = sdk.createSession(properties); @@ -380,16 +383,48 @@ public void testCreateAlwaysArchivedSession() throws OpenTokException { assertEquals(apiKey, session.getApiKey()); assertEquals(sessionId, session.getSessionId()); assertEquals(ArchiveMode.ALWAYS, session.getProperties().archiveMode()); - + assertEquals(Resolution.HD_PORTRAIT, session.getProperties().archiveResolution()); verify(postRequestedFor(urlMatching(SESSION_CREATE)) // TODO: this is a pretty bad way to verify, ideally we can decode the body and then query the object - .withRequestBody(matching(".*archiveMode=always.*"))); + .withRequestBody(matching(".*archiveMode=always.*")) + .withRequestBody(matching(".*archiveResolution=720x1280.*")) + .withRequestBody(matching(".*archiveName=720pTest.*"))); assertTrue(Helpers.verifyTokenAuth(apiKey, apiSecret, findAll(postRequestedFor(urlMatching(SESSION_CREATE))))); Helpers.verifyUserAgent(); } + @Test + public void testAutoArchiveSessionValidation() { + SessionProperties.Builder builder = new SessionProperties.Builder() + .archiveMode(ArchiveMode.ALWAYS) + .mediaMode(MediaMode.ROUTED); + + SessionProperties plain = builder.build(); + assertNull(plain.archiveName()); + assertNull(plain.archiveResolution()); + + assertEquals(1, builder.archiveName("A").build().archiveName().length()); + assertThrows(IllegalArgumentException.class, () -> builder.archiveName("").build()); + StringBuilder sb = new StringBuilder(80); + for (int i = 0; i < 10; sb.append("Archive").append(i++)); + assertEquals(80, builder.archiveName(sb.toString()).build().archiveName().length()); + assertThrows(IllegalArgumentException.class, () -> builder.archiveName(sb.append("N").toString()).build()); + + builder.archiveName("Test").archiveMode(ArchiveMode.MANUAL); + assertThrows(IllegalStateException.class, builder::build); + + SessionProperties fhd = builder + .archiveMode(ArchiveMode.ALWAYS) + .archiveResolution(Resolution.FHD_LANDSCAPE) + .archiveName(null).build(); + assertEquals("1920x1080", fhd.archiveResolution().toString()); + assertNull(fhd.archiveName()); + + assertThrows(IllegalStateException.class, () -> builder.archiveMode(ArchiveMode.MANUAL).build()); + } + @Test(expected = InvalidArgumentException.class) public void testCreateBadSession() throws OpenTokException { SessionProperties properties = new SessionProperties.Builder() From edf0591e89d410441b875509be874b0b060b5332 Mon Sep 17 00:00:00 2001 From: Sina Madani Date: Tue, 6 Jun 2023 16:59:04 +0100 Subject: [PATCH 3/3] Updated resolution names --- src/main/java/com/opentok/Resolution.java | 14 +++++++------- src/test/java/com/opentok/test/OpenTokTest.java | 7 +++---- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/opentok/Resolution.java b/src/main/java/com/opentok/Resolution.java index 16ac227c..afd43803 100644 --- a/src/main/java/com/opentok/Resolution.java +++ b/src/main/java/com/opentok/Resolution.java @@ -15,38 +15,38 @@ import java.util.stream.Collectors; /** - * Defines valid video resolutions for an archive. + * Defines valid video resolutions. */ public enum Resolution { /** * 480p landscape (640x480) */ - SD_LANDSCAPE("640x480"), + SD_HORIZONTAL("640x480"), /** * 480p portrait (480x640) */ - SD_PORTRAIT("480x640"), + SD_VERTICAL("480x640"), /** * 720p landscape (1280x720) */ - HD_LANDSCAPE("1280x720"), + HD_HORIZONTAL("1280x720"), /** * 720p portrait (720x1280) */ - HD_PORTRAIT("720x1280"), + HD_VERTICAL("720x1280"), /** * 1080p landscape (1920x1080) */ - FHD_LANDSCAPE("1920x1080"), + FHD_HORIZONTAL("1920x1080"), /** * 1080p portrait (1080x1920) */ - FHD_PORTRAIT("1080x1920"); + FHD_VERTICAL("1080x1920"); private static final Map RESOLUTION_INDEX = Arrays.stream(Resolution.values()).collect(Collectors.toMap( diff --git a/src/test/java/com/opentok/test/OpenTokTest.java b/src/test/java/com/opentok/test/OpenTokTest.java index 4414f1a2..106119a5 100644 --- a/src/test/java/com/opentok/test/OpenTokTest.java +++ b/src/test/java/com/opentok/test/OpenTokTest.java @@ -21,7 +21,6 @@ import com.opentok.exception.OpenTokException; import com.opentok.exception.RequestException; import org.apache.commons.lang.StringUtils; -import org.checkerframework.common.returnsreceiver.qual.This; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -374,7 +373,7 @@ public void testCreateAlwaysArchivedSession() throws OpenTokException { SessionProperties properties = new SessionProperties.Builder() .archiveMode(ArchiveMode.ALWAYS) .mediaMode(MediaMode.ROUTED) - .archiveResolution(Resolution.HD_PORTRAIT) + .archiveResolution(Resolution.HD_VERTICAL) .archiveName("720pTest") .build(); Session session = sdk.createSession(properties); @@ -383,7 +382,7 @@ public void testCreateAlwaysArchivedSession() throws OpenTokException { assertEquals(apiKey, session.getApiKey()); assertEquals(sessionId, session.getSessionId()); assertEquals(ArchiveMode.ALWAYS, session.getProperties().archiveMode()); - assertEquals(Resolution.HD_PORTRAIT, session.getProperties().archiveResolution()); + assertEquals(Resolution.HD_VERTICAL, session.getProperties().archiveResolution()); verify(postRequestedFor(urlMatching(SESSION_CREATE)) // TODO: this is a pretty bad way to verify, ideally we can decode the body and then query the object @@ -417,7 +416,7 @@ public void testAutoArchiveSessionValidation() { SessionProperties fhd = builder .archiveMode(ArchiveMode.ALWAYS) - .archiveResolution(Resolution.FHD_LANDSCAPE) + .archiveResolution(Resolution.FHD_HORIZONTAL) .archiveName(null).build(); assertEquals("1920x1080", fhd.archiveResolution().toString()); assertNull(fhd.archiveName());