diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index 367bb9638..05909e3b1 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -23,7 +23,7 @@ jobs:
path: ~/.gradle/caches
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }}
restore-keys: ${{ runner.os }}-gradle
- - name: Publish with gradle
+ - name: Publish with Gradle
env:
signingKey: ${{secrets.SIGNING_KEY}}
signingPassword: ${{secrets.SIGNING_PASSWORD}}
diff --git a/.gitignore b/.gitignore
index f3b6b5586..0e8afb0ed 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,3 @@
-src/test/java/com/vonage/client/BugRepro.java
*.groovy
.classpath
.gradle
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 39f002399..848622d10 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
# [7.6.0] - 2023-06-29
- Added Proactive Connect API implementation
+- Added Meetings API implementation
- Updated Subaccounts name & secret validation logic
# [7.5.0] - 2023-06-14
diff --git a/README.md b/README.md
index 77e36c94f..ef9da99ca 100644
--- a/README.md
+++ b/README.md
@@ -88,7 +88,7 @@ to your project's classpath.
* There are also **many useful code samples** in our [nexmo-community/nexmo-java-quickstart](https://github.com/nexmo-community/nexmo-java-quickstart) repository.
### Customize the Base URI
-By default, the client will use https://api.nexmo.com, https://rest.nexmo.com, and https://sns.nexmo.com as base URIs for the various endpoints. To customize these you can instantiate `VonageClient` with an `HttpConfig` object.
+By default, the client will use https://api.nexmo.com, https://rest.nexmo.com, https://sns.nexmo.com and https://api-eu.vonage.com as base URIs for the various endpoints. To customize these you can instantiate `VonageClient` with an `HttpConfig` object.
`HttpConfig.Builder` has been created to assist in building this object. Usage is as follows:
@@ -97,6 +97,7 @@ HttpConfig httpConfig = HttpConfig.builder()
.apiBaseUri("https://api.example.com")
.restBaseUri("https://rest.example.com")
.snsBaseUri("https://sns.example.com")
+ .apiEuBaseUri("https://api-eu.example.com")
.build();
VonageClient client = VonageClient.builder()
@@ -524,8 +525,11 @@ Our [Voice API](https://developer.vonage.com/voice/voice-api/overview) can conne
## Frequently Asked Questions
-Q: Does this SDK support thread safety?
-A: No, it currently does not.
+Q: What is your policy on thread safety?
+
+A: The current architecture of the SDK means that only one thread should use the client at a time.
+If you would like to use the SDK in a multithreaded environment, create a separate instance of
+`VonageClient` for each thread.
Q: Does this SDK support asynchronous request / response processing?
A: Currently no, but it is on the roadmap.
@@ -544,6 +548,7 @@ The following is a list of Vonage APIs and whether the Java SDK provides support
| Dispatch | Beta | ❌ |
| External Accounts | Developer Preview | ❌ |
| Media | Beta | ❌ |
+| Meetings | General Availability | ✅ |
| Messages | General Availability | ✅ |
| Number Insight | General Availability | ✅ |
| Number Management | General Availability | ✅ |
diff --git a/build.gradle b/build.gradle
index 38912f4b2..9d5f1cd56 100644
--- a/build.gradle
+++ b/build.gradle
@@ -28,6 +28,7 @@ dependencies {
implementation 'commons-codec:commons-codec:1.15'
implementation 'org.apache.httpcomponents:httpclient:4.5.14'
+ implementation 'org.apache.httpcomponents:httpmime:4.5.14'
implementation 'com.fasterxml.jackson.core:jackson-databind:2.15.2'
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.15.2'
implementation 'io.openapitools.jackson.dataformat:jackson-dataformat-hal:1.0.9'
@@ -36,8 +37,8 @@ dependencies {
testImplementation 'junit:junit:4.13.2'
testImplementation 'org.mockito:mockito-inline:4.11.0'
testImplementation 'org.hamcrest:hamcrest-all:1.3'
- testImplementation 'org.springframework:spring-test:5.3.27'
- testImplementation 'org.springframework:spring-web:5.3.27'
+ testImplementation 'org.springframework:spring-test:5.3.28'
+ testImplementation 'org.springframework:spring-web:5.3.28'
testImplementation 'jakarta.servlet:jakarta.servlet-api:4.0.4'
}
diff --git a/src/main/java/com/vonage/client/VonageApiResponseException.java b/src/main/java/com/vonage/client/VonageApiResponseException.java
index ef993b194..08a3b2724 100644
--- a/src/main/java/com/vonage/client/VonageApiResponseException.java
+++ b/src/main/java/com/vonage/client/VonageApiResponseException.java
@@ -27,6 +27,7 @@
import org.apache.http.util.EntityUtils;
import java.io.IOException;
import java.net.URI;
+import java.util.List;
import java.util.Objects;
/**
@@ -38,8 +39,24 @@
public abstract class VonageApiResponseException extends VonageClientException {
protected URI type;
protected String title, detail, instance;
+ protected List> errors;
@JsonIgnore protected int statusCode;
+ protected VonageApiResponseException() {
+ }
+
+ protected VonageApiResponseException(String message) {
+ super(message);
+ }
+
+ protected VonageApiResponseException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ protected VonageApiResponseException(Throwable cause) {
+ super(cause);
+ }
+
/**
* Link to the API error type.
*
@@ -80,6 +97,18 @@ public String getInstance() {
return instance;
}
+ /**
+ * Additional description of problems encountered with the request.
+ * This is typically only applicable to 400 or 409 error codes.
+ *
+ * @return The list of errors returned from the server (could be a Map or String),
+ * or {@code null} if none / not applicable.
+ */
+ @JsonProperty("errors")
+ public List> getErrors() {
+ return errors;
+ }
+
/**
* The API response status code, usually 4xx or 500.
*
@@ -145,9 +174,9 @@ public String toJson() {
protected static E fromJson(Class clazz, String json) {
if (json == null || json.length() < 2) {
try {
- return clazz.newInstance();
+ return clazz.getConstructor().newInstance();
}
- catch (InstantiationException | IllegalAccessException ex) {
+ catch (Exception ex) {
throw new VonageUnexpectedException(ex);
}
}
@@ -156,7 +185,7 @@ protected static E fromJson(Class claz
return mapper.readValue(json, clazz);
}
catch (IOException ex) {
- throw new VonageUnexpectedException("Failed to produce "+clazz.getSimpleName()+" from json.", ex);
+ throw new VonageResponseParseException("Failed to produce "+clazz.getSimpleName()+" from json.", ex);
}
}
diff --git a/src/main/java/com/vonage/client/VonageClient.java b/src/main/java/com/vonage/client/VonageClient.java
index 20d68a38f..f71608cff 100644
--- a/src/main/java/com/vonage/client/VonageClient.java
+++ b/src/main/java/com/vonage/client/VonageClient.java
@@ -21,6 +21,7 @@
import com.vonage.client.auth.hashutils.HashUtil;
import com.vonage.client.conversion.ConversionClient;
import com.vonage.client.insight.InsightClient;
+import com.vonage.client.meetings.MeetingsClient;
import com.vonage.client.messages.MessagesClient;
import com.vonage.client.numbers.NumbersClient;
import com.vonage.client.proactiveconnect.ProactiveConnectClient;
@@ -62,6 +63,7 @@ public class VonageClient {
private final Verify2Client verify2;
private final SubaccountsClient subaccounts;
private final ProactiveConnectClient proactiveConnect;
+ private final MeetingsClient meetings;
private VonageClient(Builder builder) {
httpWrapper = new HttpWrapper(builder.httpConfig, builder.authCollection);
@@ -81,6 +83,7 @@ private VonageClient(Builder builder) {
verify2 = new Verify2Client(httpWrapper);
subaccounts = new SubaccountsClient(httpWrapper);
proactiveConnect = new ProactiveConnectClient(httpWrapper);
+ meetings = new MeetingsClient(httpWrapper);
}
public AccountClient getAccountClient() {
@@ -147,6 +150,15 @@ public ProactiveConnectClient getProactiveConnectClient() {
return proactiveConnect;
}
+ /**
+ *
+ * @return The Meetings client.
+ * @since 7.6.0
+ */
+ public MeetingsClient getMeetingsClient() {
+ return meetings;
+ }
+
/**
*
* @return The Verify v2 client.
diff --git a/src/main/java/com/vonage/client/meetings/Application.java b/src/main/java/com/vonage/client/meetings/Application.java
new file mode 100644
index 000000000..17e5d422f
--- /dev/null
+++ b/src/main/java/com/vonage/client/meetings/Application.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2023 Vonage
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.vonage.client.meetings;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.vonage.client.VonageResponseParseException;
+import java.io.IOException;
+import java.util.UUID;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class Application {
+ private String accountId;
+ private UUID applicationId, defaultThemeId;
+
+ protected Application() {
+ }
+
+ /**
+ * ID of the application.
+ *
+ * @return The application ID.
+ */
+ @JsonProperty("application_id")
+ public UUID getApplicationId() {
+ return applicationId;
+ }
+
+ /**
+ * ID of the account application.
+ *
+ * @return The account ID.
+ */
+ @JsonProperty("account_id")
+ public String getAccountId() {
+ return accountId;
+ }
+
+ /**
+ * ID of the default theme.
+ *
+ * @return The application default theme ID.
+ */
+ @JsonProperty("default_theme_id")
+ public UUID getDefaultThemeId() {
+ return defaultThemeId;
+ }
+
+ /**
+ * Creates an instance of this class from a JSON payload.
+ *
+ * @param json The JSON string to parse.
+ * @return An instance of this class with the fields populated, if present.
+ */
+ public static Application fromJson(String json) {
+ try {
+ ObjectMapper mapper = new ObjectMapper();
+ return mapper.readValue(json, Application.class);
+ }
+ catch (IOException ex) {
+ throw new VonageResponseParseException("Failed to produce Application from json.", ex);
+ }
+ }
+}
diff --git a/src/main/java/com/vonage/client/meetings/AvailableFeatures.java b/src/main/java/com/vonage/client/meetings/AvailableFeatures.java
new file mode 100644
index 000000000..9476490bd
--- /dev/null
+++ b/src/main/java/com/vonage/client/meetings/AvailableFeatures.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright 2023 Vonage
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.vonage.client.meetings;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+@JsonInclude(value = JsonInclude.Include.NON_NULL)
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class AvailableFeatures {
+ private Boolean isRecordingAvailable, isChatAvailable, isWhiteboardAvailable, isLocaleSwitcherAvailable;
+
+ protected AvailableFeatures() {
+ }
+
+ AvailableFeatures(Builder builder) {
+ isRecordingAvailable = builder.isRecordingAvailable;
+ isChatAvailable = builder.isChatAvailable;
+ isWhiteboardAvailable = builder.isWhiteboardAvailable;
+ isLocaleSwitcherAvailable = builder.isLocaleSwitcherAvailable;
+ }
+
+ /**
+ * Determine if recording feature is available in the UI.
+ *
+ * @return {@code true} if the feature is available.
+ */
+ @JsonProperty("is_recording_available")
+ public Boolean getIsRecordingAvailable() {
+ return isRecordingAvailable;
+ }
+
+ /**
+ * Determine if chat feature is available in the UI.
+ *
+ * @return {@code true} if the feature is available.
+ */
+ @JsonProperty("is_chat_available")
+ public Boolean getIsChatAvailable() {
+ return isChatAvailable;
+ }
+
+ /**
+ * Determine if whiteboard feature is available in the UI.
+ *
+ * @return {@code true} if the feature is available.
+ */
+ @JsonProperty("is_whiteboard_available")
+ public Boolean getIsWhiteboardAvailable() {
+ return isWhiteboardAvailable;
+ }
+
+ /**
+ * Determine if locale switcher feature is available in the UI.
+ *
+ * @return {@code true} if the feature is available.
+ */
+ @JsonProperty("is_locale_switcher_available")
+ public Boolean getIsLocaleSwitcherAvailable() {
+ return isLocaleSwitcherAvailable;
+ }
+
+ /**
+ * Entry point for constructing an instance of this class.
+ *
+ * @return A new Builder.
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static class Builder {
+ private Boolean isRecordingAvailable, isChatAvailable, isWhiteboardAvailable, isLocaleSwitcherAvailable;
+
+ Builder() {}
+
+ /**
+ *
+ * @param isRecordingAvailable Determine if recording feature is available in the UI.
+ *
+ * @return This builder.
+ */
+ public Builder isRecordingAvailable(boolean isRecordingAvailable) {
+ this.isRecordingAvailable = isRecordingAvailable;
+ return this;
+ }
+
+ /**
+ *
+ * @param isChatAvailable Determine if chat feature is available in the UI.
+ *
+ * @return This builder.
+ */
+ public Builder isChatAvailable(boolean isChatAvailable) {
+ this.isChatAvailable = isChatAvailable;
+ return this;
+ }
+
+ /**
+ *
+ * @param isWhiteboardAvailable Determine if whiteboard feature is available in the UI.
+ *
+ * @return This builder.
+ */
+ public Builder isWhiteboardAvailable(boolean isWhiteboardAvailable) {
+ this.isWhiteboardAvailable = isWhiteboardAvailable;
+ return this;
+ }
+
+ /**
+ *
+ * @param isLocaleSwitcherAvailable Determine if locale switcher feature is available in the UI.
+ *
+ * @return This builder.
+ */
+ public Builder isLocaleSwitcherAvailable(boolean isLocaleSwitcherAvailable) {
+ this.isLocaleSwitcherAvailable = isLocaleSwitcherAvailable;
+ return this;
+ }
+
+ /**
+ * Builds the {@linkplain AvailableFeatures}.
+ *
+ * @return An instance of AvailableFeatures, populated with all fields from this builder.
+ */
+ public AvailableFeatures build() {
+ return new AvailableFeatures(this);
+ }
+ }
+}
diff --git a/src/main/java/com/vonage/client/meetings/CallbackUrls.java b/src/main/java/com/vonage/client/meetings/CallbackUrls.java
new file mode 100644
index 000000000..f1efdc0bb
--- /dev/null
+++ b/src/main/java/com/vonage/client/meetings/CallbackUrls.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2023 Vonage
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.vonage.client.meetings;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.net.URI;
+
+@JsonInclude(value = JsonInclude.Include.NON_NULL)
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class CallbackUrls {
+ private URI roomsCallbackUrl, sessionsCallbackUrl, recordingsCallbackUrl;
+
+ protected CallbackUrls() {
+ }
+
+ CallbackUrls(Builder builder) {
+ if (builder.roomsCallbackUrl != null) {
+ roomsCallbackUrl = URI.create(builder.roomsCallbackUrl);
+ }
+ if (builder.sessionsCallbackUrl != null) {
+ sessionsCallbackUrl = URI.create(builder.sessionsCallbackUrl);
+ }
+ if (builder.recordingsCallbackUrl != null) {
+ recordingsCallbackUrl = URI.create(builder.recordingsCallbackUrl);
+ }
+ }
+
+ /**
+ * Callback url for rooms events, overrides application level rooms callback URL.
+ *
+ * @return The rooms callback URL.
+ */
+ @JsonProperty("rooms_callback_url")
+ public URI getRoomsCallbackUrl() {
+ return roomsCallbackUrl;
+ }
+
+ /**
+ * Callback url for sessions events, overrides application level sessions callback URL.
+ *
+ * @return The sessions callback URL.
+ */
+ @JsonProperty("sessions_callback_url")
+ public URI getSessionsCallbackUrl() {
+ return sessionsCallbackUrl;
+ }
+
+ /**
+ * Callback url for recordings events, overrides application level recordings callback URL.
+ *
+ * @return The recordings callback URL.
+ */
+ @JsonProperty("recordings_callback_url")
+ public URI getRecordingsCallbackUrl() {
+ return recordingsCallbackUrl;
+ }
+
+ /**
+ * Entry point for constructing an instance of this class.
+ *
+ * @return A new Builder.
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static class Builder {
+ private String roomsCallbackUrl, sessionsCallbackUrl, recordingsCallbackUrl;
+
+ Builder() {}
+
+ /**
+ *
+ * @param roomsCallbackUrl Callback url for rooms events, overrides application level rooms callback url.
+ *
+ * @return This builder.
+ */
+ public Builder roomsCallbackUrl(String roomsCallbackUrl) {
+ this.roomsCallbackUrl = roomsCallbackUrl;
+ return this;
+ }
+
+ /**
+ *
+ * @param sessionsCallbackUrl Callback url for sessions events, overrides application level sessions callback url.
+ *
+ * @return This builder.
+ */
+ public Builder sessionsCallbackUrl(String sessionsCallbackUrl) {
+ this.sessionsCallbackUrl = sessionsCallbackUrl;
+ return this;
+ }
+
+ /**
+ *
+ * @param recordingsCallbackUrl Callback url for recordings events, overrides application level recordings callback url.
+ *
+ * @return This builder.
+ */
+ public Builder recordingsCallbackUrl(String recordingsCallbackUrl) {
+ this.recordingsCallbackUrl = recordingsCallbackUrl;
+ return this;
+ }
+
+
+ /**
+ * Builds the {@linkplain CallbackUrls}.
+ *
+ * @return An instance of CallbackUrls, populated with all fields from this builder.
+ */
+ public CallbackUrls build() {
+ return new CallbackUrls(this);
+ }
+ }
+}
diff --git a/src/main/java/com/vonage/client/meetings/CreateRoomEndpoint.java b/src/main/java/com/vonage/client/meetings/CreateRoomEndpoint.java
new file mode 100644
index 000000000..1e205a4c2
--- /dev/null
+++ b/src/main/java/com/vonage/client/meetings/CreateRoomEndpoint.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2023 Vonage
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.vonage.client.meetings;
+
+import com.vonage.client.AbstractMethod;
+import com.vonage.client.HttpWrapper;
+import com.vonage.client.auth.JWTAuthMethod;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.RequestBuilder;
+import org.apache.http.entity.ContentType;
+import org.apache.http.entity.StringEntity;
+import java.io.IOException;
+
+class CreateRoomEndpoint extends AbstractMethod {
+ private static final Class>[] ALLOWED_AUTH_METHODS = {JWTAuthMethod.class};
+ private static final String PATH = "/meetings/rooms";
+ private MeetingRoom cachedRoom;
+
+ CreateRoomEndpoint(HttpWrapper httpWrapper) {
+ super(httpWrapper);
+ }
+
+ @Override
+ protected Class>[] getAcceptableAuthMethods() {
+ return ALLOWED_AUTH_METHODS;
+ }
+
+ @Override
+ public RequestBuilder makeRequest(MeetingRoom request) {
+ String uri = httpWrapper.getHttpConfig().getApiEuBaseUri() + PATH;
+ return RequestBuilder.post(uri)
+ .setHeader("Content-Type", "application/json")
+ .setHeader("Accept", "application/json")
+ .setEntity(new StringEntity((cachedRoom = request).toJson(), ContentType.APPLICATION_JSON));
+ }
+
+ @Override
+ public MeetingRoom parseResponse(HttpResponse response) throws IOException {
+ try {
+ int statusCode = response.getStatusLine().getStatusCode();
+ if (statusCode >= 200 && statusCode < 300) {
+ String json = basicResponseHandler.handleResponse(response);
+ if (cachedRoom != null) {
+ cachedRoom.updateFromJson(json);
+ return cachedRoom;
+ }
+ else {
+ return MeetingRoom.fromJson(json);
+ }
+ }
+ else {
+ throw MeetingsResponseException.fromHttpResponse(response);
+ }
+ }
+ finally {
+ cachedRoom = null;
+ }
+ }
+}
diff --git a/src/main/java/com/vonage/client/meetings/CreateThemeEndpoint.java b/src/main/java/com/vonage/client/meetings/CreateThemeEndpoint.java
new file mode 100644
index 000000000..7319f4e01
--- /dev/null
+++ b/src/main/java/com/vonage/client/meetings/CreateThemeEndpoint.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2023 Vonage
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.vonage.client.meetings;
+
+import com.vonage.client.AbstractMethod;
+import com.vonage.client.HttpWrapper;
+import com.vonage.client.auth.JWTAuthMethod;
+import org.apache.http.HttpResponse;
+import org.apache.http.entity.ContentType;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.client.methods.RequestBuilder;
+import java.io.IOException;
+
+class CreateThemeEndpoint extends AbstractMethod {
+ private static final Class>[] ALLOWED_AUTH_METHODS = {JWTAuthMethod.class};
+ private static final String PATH = "/meetings/themes";
+ private Theme cachedTheme;
+
+ CreateThemeEndpoint(HttpWrapper httpWrapper) {
+ super(httpWrapper);
+ }
+
+ @Override
+ protected Class>[] getAcceptableAuthMethods() {
+ return ALLOWED_AUTH_METHODS;
+ }
+
+ @Override
+ public RequestBuilder makeRequest(Theme request) {
+ String uri = httpWrapper.getHttpConfig().getApiEuBaseUri() + PATH;
+ return RequestBuilder.post(uri)
+ .setHeader("Content-Type", "application/json")
+ .setHeader("Accept", "application/json")
+ .setEntity(new StringEntity((cachedTheme = request).toJson(), ContentType.APPLICATION_JSON));
+ }
+
+ @Override
+ public Theme parseResponse(HttpResponse response) throws IOException {
+ try {
+ int statusCode = response.getStatusLine().getStatusCode();
+ if (statusCode >= 200 && statusCode < 300) {
+ String json = basicResponseHandler.handleResponse(response);
+ if (cachedTheme != null) {
+ cachedTheme.updateFromJson(json);
+ return cachedTheme;
+ }
+ else {
+ return Theme.fromJson(json);
+ }
+ }
+ else {
+ throw MeetingsResponseException.fromHttpResponse(response);
+ }
+ }
+ finally {
+ cachedTheme = null;
+ }
+ }
+}
diff --git a/src/main/java/com/vonage/client/meetings/DeleteRecordingEndpoint.java b/src/main/java/com/vonage/client/meetings/DeleteRecordingEndpoint.java
new file mode 100644
index 000000000..392e74cde
--- /dev/null
+++ b/src/main/java/com/vonage/client/meetings/DeleteRecordingEndpoint.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2023 Vonage
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.vonage.client.meetings;
+
+import com.vonage.client.AbstractMethod;
+import com.vonage.client.HttpWrapper;
+import com.vonage.client.auth.JWTAuthMethod;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.RequestBuilder;
+import java.io.IOException;
+import java.util.UUID;
+
+class DeleteRecordingEndpoint extends AbstractMethod {
+ private static final Class>[] ALLOWED_AUTH_METHODS = {JWTAuthMethod.class};
+ private static final String PATH = "/meetings/recordings/%s";
+
+ DeleteRecordingEndpoint(HttpWrapper httpWrapper) {
+ super(httpWrapper);
+ }
+
+ @Override
+ protected Class>[] getAcceptableAuthMethods() {
+ return ALLOWED_AUTH_METHODS;
+ }
+
+ @Override
+ public RequestBuilder makeRequest(UUID request) {
+ String path = String.format(PATH, request);
+ String uri = httpWrapper.getHttpConfig().getApiEuBaseUri() + path;
+ return RequestBuilder.delete(uri)
+ .setHeader("Content-Type", "application/json");
+ }
+
+ @Override
+ public Void parseResponse(HttpResponse response) throws IOException {
+ int statusCode = response.getStatusLine().getStatusCode();
+ if (statusCode == 204) {
+ return null;
+ }
+ else {
+ throw MeetingsResponseException.fromHttpResponse(response);
+ }
+ }
+}
diff --git a/src/main/java/com/vonage/client/meetings/DeleteThemeEndpoint.java b/src/main/java/com/vonage/client/meetings/DeleteThemeEndpoint.java
new file mode 100644
index 000000000..d2ddec538
--- /dev/null
+++ b/src/main/java/com/vonage/client/meetings/DeleteThemeEndpoint.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2023 Vonage
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.vonage.client.meetings;
+
+import com.vonage.client.AbstractMethod;
+import com.vonage.client.HttpWrapper;
+import com.vonage.client.auth.JWTAuthMethod;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.RequestBuilder;
+import java.io.IOException;
+
+class DeleteThemeEndpoint extends AbstractMethod {
+ private static final Class>[] ALLOWED_AUTH_METHODS = {JWTAuthMethod.class};
+ private static final String PATH = "/meetings/themes/%s";
+
+ DeleteThemeEndpoint(HttpWrapper httpWrapper) {
+ super(httpWrapper);
+ }
+
+ @Override
+ protected Class>[] getAcceptableAuthMethods() {
+ return ALLOWED_AUTH_METHODS;
+ }
+
+ @Override
+ public RequestBuilder makeRequest(DeleteThemeRequest request) {
+ String path = String.format(PATH, request.themeId);
+ String uri = httpWrapper.getHttpConfig().getApiEuBaseUri() + path;
+ return RequestBuilder.delete(uri)
+ .addParameter("force", String.valueOf(request.force))
+ .setHeader("Content-Type", "application/json");
+ }
+
+ @Override
+ public Void parseResponse(HttpResponse response) throws IOException {
+ int statusCode = response.getStatusLine().getStatusCode();
+ if (statusCode == 204) {
+ return null;
+ }
+ else {
+ throw MeetingsResponseException.fromHttpResponse(response);
+ }
+ }
+}
diff --git a/src/main/java/com/vonage/client/meetings/DeleteThemeRequest.java b/src/main/java/com/vonage/client/meetings/DeleteThemeRequest.java
new file mode 100644
index 000000000..f519b8769
--- /dev/null
+++ b/src/main/java/com/vonage/client/meetings/DeleteThemeRequest.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2023 Vonage
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.vonage.client.meetings;
+
+import java.util.UUID;
+
+class DeleteThemeRequest {
+ final UUID themeId;
+ final boolean force;
+
+ DeleteThemeRequest(UUID themeId, boolean force) {
+ this.themeId = themeId;
+ this.force = force;
+ }
+}
diff --git a/src/main/java/com/vonage/client/meetings/DialInNumber.java b/src/main/java/com/vonage/client/meetings/DialInNumber.java
new file mode 100644
index 000000000..d3ad3bda7
--- /dev/null
+++ b/src/main/java/com/vonage/client/meetings/DialInNumber.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2023 Vonage
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.vonage.client.meetings;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.util.Locale;
+
+/**
+ * Details about a number that that can be used to dial into a meeting.
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class DialInNumber {
+ private String number, displayName;
+ private Locale locale;
+
+ protected DialInNumber() {
+ }
+
+ /**
+ * The dial-in number.
+ *
+ * @return The number in E164 format.
+ */
+ @JsonProperty("number")
+ public String getNumber() {
+ return number;
+ }
+
+ /**
+ * The number locale.
+ *
+ * @return The Locale.
+ */
+ @JsonProperty("locale")
+ public Locale getLocale() {
+ return locale;
+ }
+
+ /**
+ * The country name of the locale.
+ *
+ * @return The country's name.
+ */
+ @JsonProperty("display_name")
+ public String getDisplayName() {
+ return displayName;
+ }
+}
diff --git a/src/main/java/com/vonage/client/meetings/EventType.java b/src/main/java/com/vonage/client/meetings/EventType.java
new file mode 100644
index 000000000..30a62b5cc
--- /dev/null
+++ b/src/main/java/com/vonage/client/meetings/EventType.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2023 Vonage
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.vonage.client.meetings;
+
+import com.fasterxml.jackson.annotation.JsonValue;
+
+/**
+ * Represents the event type in {@link MeetingsEventCallback#getEvent()}.
+ */
+public enum EventType {
+ /**
+ * Room expired.
+ */
+ ROOM_EXPIRED,
+
+ /**
+ * Session started.
+ */
+ SESSION_STARTED,
+
+ /**
+ * Session ended.
+ */
+ SESSION_ENDED,
+
+ /**
+ * Recording started.
+ */
+ RECORDING_STARTED,
+
+ /**
+ * Recording ended.
+ */
+ RECORDING_ENDED,
+
+ /**
+ * Recording uploaded.
+ */
+ RECORDING_UPLOADED,
+
+ /**
+ * Participant joined.
+ */
+ SESSION_PARTICIPANT_JOINED,
+
+ /**
+ * Participant left.
+ */
+ SESSION_PARTICIPANT_LEFT;
+
+ @JsonValue
+ @Override
+ public String toString() {
+ return name().toLowerCase().replace('_', ':');
+ }
+}
diff --git a/src/main/java/com/vonage/client/meetings/FinalizeLogosEndpoint.java b/src/main/java/com/vonage/client/meetings/FinalizeLogosEndpoint.java
new file mode 100644
index 000000000..2fc529ed1
--- /dev/null
+++ b/src/main/java/com/vonage/client/meetings/FinalizeLogosEndpoint.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2023 Vonage
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.vonage.client.meetings;
+
+import com.vonage.client.AbstractMethod;
+import com.vonage.client.HttpWrapper;
+import com.vonage.client.auth.JWTAuthMethod;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.RequestBuilder;
+import org.apache.http.entity.ContentType;
+import org.apache.http.entity.StringEntity;
+import java.io.IOException;
+
+class FinalizeLogosEndpoint extends AbstractMethod {
+ private static final Class>[] ALLOWED_AUTH_METHODS = {JWTAuthMethod.class};
+ private static final String PATH = "/meetings/themes/%s/finalizeLogos";
+
+ FinalizeLogosEndpoint(HttpWrapper httpWrapper) {
+ super(httpWrapper);
+ }
+
+ @Override
+ protected Class>[] getAcceptableAuthMethods() {
+ return ALLOWED_AUTH_METHODS;
+ }
+
+ @Override
+ public RequestBuilder makeRequest(FinalizeLogosRequest request) {
+ String path = String.format(PATH, request.themeId);
+ String uri = httpWrapper.getHttpConfig().getApiEuBaseUri() + path;
+ return RequestBuilder.put(uri)
+ .setHeader("Content-Type", "application/json")
+ .setEntity(new StringEntity(request.toJson(), ContentType.APPLICATION_JSON));
+ }
+
+ @Override
+ public Void parseResponse(HttpResponse response) throws IOException {
+ int statusCode = response.getStatusLine().getStatusCode();
+ if (statusCode == 200) {
+ return null;
+ }
+ else {
+ throw MeetingsResponseException.fromHttpResponse(response);
+ }
+ }
+}
diff --git a/src/main/java/com/vonage/client/meetings/FinalizeLogosRequest.java b/src/main/java/com/vonage/client/meetings/FinalizeLogosRequest.java
new file mode 100644
index 000000000..72102d0ec
--- /dev/null
+++ b/src/main/java/com/vonage/client/meetings/FinalizeLogosRequest.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2023 Vonage
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.vonage.client.meetings;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.vonage.client.VonageUnexpectedException;
+import java.util.List;
+import java.util.UUID;
+
+@JsonInclude(value = JsonInclude.Include.NON_NULL)
+class FinalizeLogosRequest {
+ @JsonProperty("keys") List keys;
+ @JsonIgnore UUID themeId;
+
+ FinalizeLogosRequest(UUID themeId, List keys) {
+ this.keys = keys;
+ this.themeId = themeId;
+ }
+
+ String toJson() {
+ try {
+ ObjectMapper mapper = new ObjectMapper();
+ return mapper.writeValueAsString(this);
+ }
+ catch (JsonProcessingException jpe) {
+ throw new VonageUnexpectedException("Failed to produce JSON from "+getClass().getSimpleName()+" object.", jpe);
+ }
+ }
+}
diff --git a/src/main/java/com/vonage/client/meetings/GetLogoUploadUrlsEndpoint.java b/src/main/java/com/vonage/client/meetings/GetLogoUploadUrlsEndpoint.java
new file mode 100644
index 000000000..b04a810b2
--- /dev/null
+++ b/src/main/java/com/vonage/client/meetings/GetLogoUploadUrlsEndpoint.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2023 Vonage
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.vonage.client.meetings;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.vonage.client.AbstractMethod;
+import com.vonage.client.HttpWrapper;
+import com.vonage.client.VonageResponseParseException;
+import com.vonage.client.auth.JWTAuthMethod;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.RequestBuilder;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+
+class GetLogoUploadUrlsEndpoint extends AbstractMethod> {
+ private static final Class>[] ALLOWED_AUTH_METHODS = {JWTAuthMethod.class};
+ private static final String PATH = "/meetings/themes/logos-upload-urls";
+
+ GetLogoUploadUrlsEndpoint(HttpWrapper httpWrapper) {
+ super(httpWrapper);
+ }
+
+ @Override
+ protected Class>[] getAcceptableAuthMethods() {
+ return ALLOWED_AUTH_METHODS;
+ }
+
+ @Override
+ public RequestBuilder makeRequest(Void request) {
+ String uri = httpWrapper.getHttpConfig().getApiEuBaseUri() + PATH;
+ return RequestBuilder.get(uri).setHeader("Accept", "application/json");
+ }
+
+ @Override
+ public List parseResponse(HttpResponse response) throws IOException {
+ int statusCode = response.getStatusLine().getStatusCode();
+ if (statusCode >= 200 && statusCode < 300) {
+ String json = basicResponseHandler.handleResponse(response);
+ if (json == null || json.isEmpty()) {
+ return Collections.emptyList();
+ }
+ try {
+ ObjectMapper mapper = new ObjectMapper();
+ return mapper.readValue(json, new TypeReference>() {});
+ }
+ catch (IOException ex) {
+ throw new VonageResponseParseException("Failed to produce List from json.", ex);
+ }
+ }
+ else {
+ throw MeetingsResponseException.fromHttpResponse(response);
+ }
+ }
+}
diff --git a/src/main/java/com/vonage/client/meetings/GetRecordingEndpoint.java b/src/main/java/com/vonage/client/meetings/GetRecordingEndpoint.java
new file mode 100644
index 000000000..49e75313d
--- /dev/null
+++ b/src/main/java/com/vonage/client/meetings/GetRecordingEndpoint.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2023 Vonage
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.vonage.client.meetings;
+
+import com.vonage.client.AbstractMethod;
+import com.vonage.client.HttpWrapper;
+import com.vonage.client.auth.JWTAuthMethod;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.RequestBuilder;
+import java.io.IOException;
+import java.util.UUID;
+
+class GetRecordingEndpoint extends AbstractMethod {
+ private static final Class>[] ALLOWED_AUTH_METHODS = {JWTAuthMethod.class};
+ private static final String PATH = "/meetings/recordings/%s";
+
+ GetRecordingEndpoint(HttpWrapper httpWrapper) {
+ super(httpWrapper);
+ }
+
+ @Override
+ protected Class>[] getAcceptableAuthMethods() {
+ return ALLOWED_AUTH_METHODS;
+ }
+
+ @Override
+ public RequestBuilder makeRequest(UUID recordingId) {
+ String uri = httpWrapper.getHttpConfig().getApiEuBaseUri() + String.format(PATH, recordingId);
+ return RequestBuilder.get(uri).setHeader("Accept", "application/json");
+ }
+
+ @Override
+ public Recording parseResponse(HttpResponse response) throws IOException {
+ int statusCode = response.getStatusLine().getStatusCode();
+ if (statusCode >= 200 && statusCode < 300) {
+ return Recording.fromJson(basicResponseHandler.handleResponse(response));
+ }
+ else {
+ throw MeetingsResponseException.fromHttpResponse(response);
+ }
+ }
+}
diff --git a/src/main/java/com/vonage/client/meetings/GetRoomEndpoint.java b/src/main/java/com/vonage/client/meetings/GetRoomEndpoint.java
new file mode 100644
index 000000000..79cdaee27
--- /dev/null
+++ b/src/main/java/com/vonage/client/meetings/GetRoomEndpoint.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2023 Vonage
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.vonage.client.meetings;
+
+import com.vonage.client.AbstractMethod;
+import com.vonage.client.HttpWrapper;
+import com.vonage.client.auth.JWTAuthMethod;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.RequestBuilder;
+import java.io.IOException;
+import java.util.UUID;
+
+class GetRoomEndpoint extends AbstractMethod {
+ private static final Class>[] ALLOWED_AUTH_METHODS = {JWTAuthMethod.class};
+ private static final String PATH = "/meetings/rooms/%s";
+
+ GetRoomEndpoint(HttpWrapper httpWrapper) {
+ super(httpWrapper);
+ }
+
+ @Override
+ protected Class>[] getAcceptableAuthMethods() {
+ return ALLOWED_AUTH_METHODS;
+ }
+
+ @Override
+ public RequestBuilder makeRequest(UUID roomId) {
+ String uri = httpWrapper.getHttpConfig().getApiEuBaseUri() + String.format(PATH, roomId);
+ return RequestBuilder.get(uri).setHeader("Accept", "application/json");
+ }
+
+ @Override
+ public MeetingRoom parseResponse(HttpResponse response) throws IOException {
+ int statusCode = response.getStatusLine().getStatusCode();
+ if (statusCode >= 200 && statusCode < 300) {
+ return MeetingRoom.fromJson(basicResponseHandler.handleResponse(response));
+ }
+ else {
+ throw MeetingsResponseException.fromHttpResponse(response);
+ }
+ }
+}
diff --git a/src/main/java/com/vonage/client/meetings/GetThemeEndpoint.java b/src/main/java/com/vonage/client/meetings/GetThemeEndpoint.java
new file mode 100644
index 000000000..8fca41931
--- /dev/null
+++ b/src/main/java/com/vonage/client/meetings/GetThemeEndpoint.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2023 Vonage
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.vonage.client.meetings;
+
+import com.vonage.client.AbstractMethod;
+import com.vonage.client.HttpWrapper;
+import com.vonage.client.auth.JWTAuthMethod;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.RequestBuilder;
+import java.io.IOException;
+import java.util.UUID;
+
+class GetThemeEndpoint extends AbstractMethod {
+ private static final Class>[] ALLOWED_AUTH_METHODS = {JWTAuthMethod.class};
+ private static final String PATH = "/meetings/themes/%s";
+
+ GetThemeEndpoint(HttpWrapper httpWrapper) {
+ super(httpWrapper);
+ }
+
+ @Override
+ protected Class>[] getAcceptableAuthMethods() {
+ return ALLOWED_AUTH_METHODS;
+ }
+
+ @Override
+ public RequestBuilder makeRequest(UUID themeId) {
+ String uri = httpWrapper.getHttpConfig().getApiEuBaseUri() + String.format(PATH, themeId);
+ return RequestBuilder.get(uri).setHeader("Accept", "application/json");
+ }
+
+ @Override
+ public Theme parseResponse(HttpResponse response) throws IOException {
+ int statusCode = response.getStatusLine().getStatusCode();
+ if (statusCode >= 200 && statusCode < 300) {
+ return Theme.fromJson(basicResponseHandler.handleResponse(response));
+ }
+ else {
+ throw MeetingsResponseException.fromHttpResponse(response);
+ }
+ }
+}
diff --git a/src/main/java/com/vonage/client/meetings/InitialJoinOptions.java b/src/main/java/com/vonage/client/meetings/InitialJoinOptions.java
new file mode 100644
index 000000000..1b5d92422
--- /dev/null
+++ b/src/main/java/com/vonage/client/meetings/InitialJoinOptions.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2023 Vonage
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.vonage.client.meetings;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+@JsonInclude(value = JsonInclude.Include.NON_NULL)
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class InitialJoinOptions {
+ private MicrophoneState microphoneState;
+
+ protected InitialJoinOptions() {
+ }
+
+ InitialJoinOptions(Builder builder) {
+ microphoneState = builder.microphoneState;
+ }
+
+ /**
+ * The default microphone option for users in the pre-join screen of this room.
+ *
+ * @return The microphone state, as an enum
+ */
+ @JsonProperty("microphone_state")
+ public MicrophoneState getMicrophoneState() {
+ return microphoneState;
+ }
+
+ /**
+ * Entry point for constructing an instance of this class.
+ *
+ * @return A new Builder.
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static class Builder {
+ private MicrophoneState microphoneState;
+
+ Builder() {}
+
+ /**
+ *
+ * @param microphoneState Set the default microphone option for users in the pre-join screen of this room.
+ *
+ * @return This builder.
+ */
+ public Builder microphoneState(MicrophoneState microphoneState) {
+ this.microphoneState = microphoneState;
+ return this;
+ }
+
+
+ /**
+ * Builds the {@linkplain InitialJoinOptions}.
+ *
+ * @return An instance of InitialJoinOptions, populated with all fields from this builder.
+ */
+ public InitialJoinOptions build() {
+ return new InitialJoinOptions(this);
+ }
+ }
+}
diff --git a/src/main/java/com/vonage/client/meetings/JoinApprovalLevel.java b/src/main/java/com/vonage/client/meetings/JoinApprovalLevel.java
new file mode 100644
index 000000000..fdc1ac2bc
--- /dev/null
+++ b/src/main/java/com/vonage/client/meetings/JoinApprovalLevel.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2023 Vonage
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.vonage.client.meetings;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonValue;
+
+/**
+ * Represents the level of approval needed to join the meeting in the room.
+ */
+public enum JoinApprovalLevel {
+ /**
+ * Participants can join the meeting at any time without approval.
+ */
+ NONE,
+
+ /**
+ * Participants will join the meeting only after the host joined.
+ */
+ AFTER_OWNER_ONLY,
+
+ /**
+ * Participants will join the waiting room and the host will deny/approve them.
+ */
+ EXPLICIT_APPROVAL;
+
+ @JsonCreator
+ public static JoinApprovalLevel fromString(String value) {
+ try {
+ return valueOf(value.toUpperCase());
+ }
+ catch (NullPointerException | IllegalArgumentException ex) {
+ return null;
+ }
+ }
+
+ @JsonValue
+ @Override
+ public String toString() {
+ return name().toLowerCase();
+ }
+}
diff --git a/src/main/java/com/vonage/client/meetings/ListDialNumbersEndpoint.java b/src/main/java/com/vonage/client/meetings/ListDialNumbersEndpoint.java
new file mode 100644
index 000000000..d1e907a6e
--- /dev/null
+++ b/src/main/java/com/vonage/client/meetings/ListDialNumbersEndpoint.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2023 Vonage
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.vonage.client.meetings;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.vonage.client.AbstractMethod;
+import com.vonage.client.HttpWrapper;
+import com.vonage.client.auth.JWTAuthMethod;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.RequestBuilder;
+import java.io.IOException;
+import java.util.List;
+
+class ListDialNumbersEndpoint extends AbstractMethod> {
+ private static final Class>[] ALLOWED_AUTH_METHODS = {JWTAuthMethod.class};
+ private static final String PATH = "/meetings/dial-in-numbers";
+
+ ListDialNumbersEndpoint(HttpWrapper httpWrapper) {
+ super(httpWrapper);
+ }
+
+ @Override
+ protected Class>[] getAcceptableAuthMethods() {
+ return ALLOWED_AUTH_METHODS;
+ }
+
+ @Override
+ public RequestBuilder makeRequest(Void request) {
+ String uri = httpWrapper.getHttpConfig().getApiEuBaseUri() + PATH;
+ return RequestBuilder.get(uri).setHeader("Accept", "application/json");
+ }
+
+ @Override
+ public List parseResponse(HttpResponse response) throws IOException {
+ int statusCode = response.getStatusLine().getStatusCode();
+ if (statusCode >= 200 && statusCode < 300) {
+ String json = basicResponseHandler.handleResponse(response);
+ ObjectMapper mapper = new ObjectMapper();
+ return mapper.readValue(json, new TypeReference>() {});
+ }
+ else {
+ throw MeetingsResponseException.fromHttpResponse(response);
+ }
+ }
+}
diff --git a/src/main/java/com/vonage/client/meetings/ListRecordingsEndpoint.java b/src/main/java/com/vonage/client/meetings/ListRecordingsEndpoint.java
new file mode 100644
index 000000000..14a1bcdf4
--- /dev/null
+++ b/src/main/java/com/vonage/client/meetings/ListRecordingsEndpoint.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2023 Vonage
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.vonage.client.meetings;
+
+import com.vonage.client.AbstractMethod;
+import com.vonage.client.HttpWrapper;
+import com.vonage.client.auth.JWTAuthMethod;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.RequestBuilder;
+import java.io.IOException;
+
+class ListRecordingsEndpoint extends AbstractMethod {
+ private static final Class>[] ALLOWED_AUTH_METHODS = {JWTAuthMethod.class};
+ private static final String PATH = "/meetings/sessions/%s/recordings";
+
+ ListRecordingsEndpoint(HttpWrapper httpWrapper) {
+ super(httpWrapper);
+ }
+
+ @Override
+ protected Class>[] getAcceptableAuthMethods() {
+ return ALLOWED_AUTH_METHODS;
+ }
+
+ @Override
+ public RequestBuilder makeRequest(String sessionId) {
+ String uri = httpWrapper.getHttpConfig().getApiEuBaseUri() + String.format(PATH, sessionId);
+ return RequestBuilder.get(uri).setHeader("Accept", "application/json");
+ }
+
+ @Override
+ public ListRecordingsResponse parseResponse(HttpResponse response) throws IOException {
+ int statusCode = response.getStatusLine().getStatusCode();
+ if (statusCode >= 200 && statusCode < 300) {
+ String json = basicResponseHandler.handleResponse(response);
+ if (json == null || json.isEmpty()) {
+ return new ListRecordingsResponse();
+ }
+ return ListRecordingsResponse.fromJson(json);
+ }
+ else {
+ throw MeetingsResponseException.fromHttpResponse(response);
+ }
+ }
+}
diff --git a/src/main/java/com/vonage/client/meetings/ListRecordingsResponse.java b/src/main/java/com/vonage/client/meetings/ListRecordingsResponse.java
new file mode 100644
index 000000000..de6aeb08d
--- /dev/null
+++ b/src/main/java/com/vonage/client/meetings/ListRecordingsResponse.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2023 Vonage
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.vonage.client.meetings;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
+import com.vonage.client.VonageResponseParseException;
+import java.io.IOException;
+import java.util.List;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+class ListRecordingsResponse {
+ @JsonProperty("_embedded") private Embedded embedded;
+
+ static class Embedded {
+ @JsonProperty("recordings") List recordings;
+ }
+
+ public List getRecordings() {
+ return embedded != null ? embedded.recordings : null;
+ }
+
+ public static ListRecordingsResponse fromJson(String json) {
+ try {
+ ObjectMapper mapper = new ObjectMapper();
+ mapper.registerModule(new JavaTimeModule());
+ return mapper.readValue(json, ListRecordingsResponse.class);
+ }
+ catch (IOException ex) {
+ throw new VonageResponseParseException("Failed to produce ListRecordingsResponse from json.", ex);
+ }
+ }
+}
diff --git a/src/main/java/com/vonage/client/meetings/ListRoomsEndpoint.java b/src/main/java/com/vonage/client/meetings/ListRoomsEndpoint.java
new file mode 100644
index 000000000..59e71d191
--- /dev/null
+++ b/src/main/java/com/vonage/client/meetings/ListRoomsEndpoint.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2023 Vonage
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.vonage.client.meetings;
+
+import com.vonage.client.AbstractMethod;
+import com.vonage.client.HttpWrapper;
+import com.vonage.client.auth.JWTAuthMethod;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.RequestBuilder;
+import java.io.IOException;
+
+class ListRoomsEndpoint extends AbstractMethod {
+ private static final Class>[] ALLOWED_AUTH_METHODS = {JWTAuthMethod.class};
+ private static final String PATH = "/meetings/rooms";
+
+ ListRoomsEndpoint(HttpWrapper httpWrapper) {
+ super(httpWrapper);
+ }
+
+ @Override
+ protected Class>[] getAcceptableAuthMethods() {
+ return ALLOWED_AUTH_METHODS;
+ }
+
+ @Override
+ public RequestBuilder makeRequest(ListRoomsRequest request) {
+ String uri = httpWrapper.getHttpConfig().getApiEuBaseUri() + PATH;
+ return request.addParameters(RequestBuilder.get(uri))
+ .setHeader("Accept", "application/json");
+ }
+
+ @Override
+ public ListRoomsResponse parseResponse(HttpResponse response) throws IOException {
+ int statusCode = response.getStatusLine().getStatusCode();
+ if (statusCode >= 200 && statusCode < 300) {
+ return ListRoomsResponse.fromJson(basicResponseHandler.handleResponse(response));
+ }
+ else {
+ throw MeetingsResponseException.fromHttpResponse(response);
+ }
+ }
+}
diff --git a/src/main/java/com/vonage/client/meetings/ListRoomsRequest.java b/src/main/java/com/vonage/client/meetings/ListRoomsRequest.java
new file mode 100644
index 000000000..4188044be
--- /dev/null
+++ b/src/main/java/com/vonage/client/meetings/ListRoomsRequest.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2023 Vonage
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.vonage.client.meetings;
+
+import org.apache.http.client.methods.RequestBuilder;
+import java.util.UUID;
+
+class ListRoomsRequest {
+ final UUID themeId;
+ final Integer pageSize, startId, endId;
+
+ ListRoomsRequest(Integer startId, Integer endId, Integer pageSize, UUID themeId) {
+ this.themeId = themeId;
+ this.startId = startId;
+ this.endId = endId;
+ this.pageSize = pageSize;
+ }
+
+ RequestBuilder addParameters(RequestBuilder builder) {
+ if (startId != null) {
+ builder.addParameter("start_id", String.valueOf(startId));
+ }
+ if (endId != null) {
+ builder.addParameter("end_id", String.valueOf(endId));
+ }
+ if (pageSize != null) {
+ builder.addParameter("page_size", String.valueOf(pageSize));
+ }
+ return builder;
+ }
+}
diff --git a/src/main/java/com/vonage/client/meetings/ListRoomsResponse.java b/src/main/java/com/vonage/client/meetings/ListRoomsResponse.java
new file mode 100644
index 000000000..0e7c1fb67
--- /dev/null
+++ b/src/main/java/com/vonage/client/meetings/ListRoomsResponse.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2023 Vonage
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.vonage.client.meetings;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
+import com.vonage.client.VonageResponseParseException;
+import com.vonage.client.common.HalPageResponse;
+import java.io.IOException;
+import java.util.List;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+class ListRoomsResponse extends HalPageResponse {
+ @JsonProperty("_embedded") private List rooms;
+
+ protected ListRoomsResponse() {
+ }
+
+ /**
+ * The embedded response containing the list of meeting rooms.
+ *
+ * @return The list of rooms.
+ */
+ public List getMeetingRooms() {
+ return rooms;
+ }
+
+ /**
+ * Creates an instance of this class from a JSON payload.
+ *
+ * @param json The JSON string to parse.
+ * @return An instance of this class with the fields populated, if present.
+ */
+ public static ListRoomsResponse fromJson(String json) {
+ try {
+ ObjectMapper mapper = new ObjectMapper();
+ mapper.registerModule(new JavaTimeModule());
+ return mapper.readValue(json, ListRoomsResponse.class);
+ }
+ catch (IOException ex) {
+ throw new VonageResponseParseException("Failed to produce ListRoomsResponse from json.", ex);
+ }
+ }
+}
diff --git a/src/main/java/com/vonage/client/meetings/ListThemesEndpoint.java b/src/main/java/com/vonage/client/meetings/ListThemesEndpoint.java
new file mode 100644
index 000000000..7100f98f0
--- /dev/null
+++ b/src/main/java/com/vonage/client/meetings/ListThemesEndpoint.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2023 Vonage
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.vonage.client.meetings;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.vonage.client.AbstractMethod;
+import com.vonage.client.HttpWrapper;
+import com.vonage.client.auth.JWTAuthMethod;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.RequestBuilder;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+
+class ListThemesEndpoint extends AbstractMethod> {
+ private static final Class>[] ALLOWED_AUTH_METHODS = {JWTAuthMethod.class};
+ private static final String PATH = "/meetings/themes";
+
+ ListThemesEndpoint(HttpWrapper httpWrapper) {
+ super(httpWrapper);
+ }
+
+ @Override
+ protected Class>[] getAcceptableAuthMethods() {
+ return ALLOWED_AUTH_METHODS;
+ }
+
+ @Override
+ public RequestBuilder makeRequest(Void request) {
+ String uri = httpWrapper.getHttpConfig().getApiEuBaseUri() + PATH;
+ return RequestBuilder.get(uri).setHeader("Accept", "application/json");
+ }
+
+ @Override
+ public List parseResponse(HttpResponse response) throws IOException {
+ int statusCode = response.getStatusLine().getStatusCode();
+ if (statusCode >= 200 && statusCode < 300) {
+ String json = basicResponseHandler.handleResponse(response);
+ if (json == null || json.isEmpty()) {
+ return Collections.emptyList();
+ }
+ ObjectMapper mapper = new ObjectMapper();
+ return mapper.readValue(json, new TypeReference>() {});
+ }
+ else {
+ throw MeetingsResponseException.fromHttpResponse(response);
+ }
+ }
+}
diff --git a/src/main/java/com/vonage/client/meetings/LogoType.java b/src/main/java/com/vonage/client/meetings/LogoType.java
new file mode 100644
index 000000000..3ea45812f
--- /dev/null
+++ b/src/main/java/com/vonage/client/meetings/LogoType.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2023 Vonage
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.vonage.client.meetings;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonValue;
+
+/**
+ * Represents the type of logo returned in {@link LogoUploadsUrlResponse.Fields}.
+ */
+public enum LogoType {
+ WHITE,
+ COLORED,
+ FAVICON;
+
+ @JsonCreator
+ public static LogoType fromString(String value) {
+ try {
+ return valueOf(value.toUpperCase());
+ }
+ catch (NullPointerException | IllegalArgumentException ex) {
+ return null;
+ }
+ }
+
+ @JsonValue
+ @Override
+ public String toString() {
+ return name().toLowerCase();
+ }
+}
diff --git a/src/main/java/com/vonage/client/meetings/LogoUploadsUrlResponse.java b/src/main/java/com/vonage/client/meetings/LogoUploadsUrlResponse.java
new file mode 100644
index 000000000..349bfc682
--- /dev/null
+++ b/src/main/java/com/vonage/client/meetings/LogoUploadsUrlResponse.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright 2023 Vonage
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.vonage.client.meetings;
+
+import com.fasterxml.jackson.annotation.JsonGetter;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonSetter;
+import com.vonage.client.VonageResponseParseException;
+import javax.activation.MimeType;
+import javax.activation.MimeTypeParseException;
+import java.net.URI;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class LogoUploadsUrlResponse {
+ private URI url;
+ private Fields fields;
+
+ protected LogoUploadsUrlResponse() {
+ }
+
+ /**
+ * Storage system URL.
+ *
+ * @return The URL.
+ */
+ @JsonProperty("url")
+ public URI getUrl() {
+ return url;
+ }
+
+ /**
+ * Fields property has to be part of the POST request's body.
+ *
+ * @return The field properties.
+ */
+ @JsonProperty("fields")
+ public Fields getFields() {
+ return fields;
+ }
+
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public static class Fields {
+ private LogoType logoType;
+ private MimeType contentType;
+ private String key, bucket, policy, amzDate, amzAlgorithm, amzCredential, amzSecurityToken, amzSignature;
+
+ protected Fields() {
+ }
+
+ /**
+ * MIME type of the content.
+ *
+ * @return The image type.
+ */
+ @JsonGetter("Content-Type")
+ public MimeType getContentType() {
+ return contentType;
+ }
+
+ @JsonSetter("Content-Type")
+ protected void setContentType(String contentType) {
+ try {
+ this.contentType = contentType != null ? new MimeType(contentType) : null;
+ }
+ catch (MimeTypeParseException ex) {
+ throw new VonageResponseParseException("Invalid MIME type: "+contentType, ex);
+ }
+ }
+
+ /**
+ * Logo's key in storage system.
+ *
+ * @return The logo key.
+ */
+ @JsonProperty("key")
+ public String getKey() {
+ return key;
+ }
+
+ /**
+ * Logo's type.
+ *
+ * @return The logo type, as an enum.
+ */
+ @JsonProperty("logoType")
+ public LogoType getLogoType() {
+ return logoType;
+ }
+
+ /**
+ * Bucket name to upload to.
+ *
+ * @return The bucket name.
+ */
+ @JsonProperty("bucket")
+ public String getBucket() {
+ return bucket;
+ }
+
+ @JsonProperty("X-Amz-Algorithm")
+ public String getAmzAlgorithm() {
+ return amzAlgorithm;
+ }
+
+ @JsonProperty("X-Amz-Credential")
+ public String getAmzCredential() {
+ return amzCredential;
+ }
+
+ @JsonProperty("X-Amz-Date")
+ public String getAmzDate() {
+ return amzDate;
+ }
+
+ @JsonProperty("X-Amz-Security-Token")
+ public String getAmzSecurityToken() {
+ return amzSecurityToken;
+ }
+
+ @JsonProperty("Policy")
+ public String getPolicy() {
+ return policy;
+ }
+
+ @JsonProperty("X-Amz-Signature")
+ public String getAmzSignature() {
+ return amzSignature;
+ }
+ }
+}
diff --git a/src/main/java/com/vonage/client/meetings/MeetingRoom.java b/src/main/java/com/vonage/client/meetings/MeetingRoom.java
new file mode 100644
index 000000000..342df0d6c
--- /dev/null
+++ b/src/main/java/com/vonage/client/meetings/MeetingRoom.java
@@ -0,0 +1,488 @@
+/*
+ * Copyright 2023 Vonage
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.vonage.client.meetings;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
+import com.vonage.client.VonageResponseParseException;
+import com.vonage.client.VonageUnexpectedException;
+import java.io.IOException;
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
+import java.util.UUID;
+
+@JsonInclude(value = JsonInclude.Include.NON_NULL)
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class MeetingRoom {
+ private Instant createdAt, expiresAt;
+ private UUID id, themeId;
+ private String displayName, metadata, meetingCode;
+ private Boolean isAvailable, expireAfterUse;
+ private RoomType type;
+ private JoinApprovalLevel joinApprovalLevel;
+ private RecordingOptions recordingOptions;
+ private InitialJoinOptions initialJoinOptions;
+ private UISettings uiSettings;
+ private CallbackUrls callbackUrls;
+ private AvailableFeatures availableFeatures;
+ private RoomLinks links;
+
+ protected MeetingRoom() {
+ }
+
+ MeetingRoom(Builder builder) {
+ if ((displayName = builder.displayName) == null || displayName.trim().isEmpty()) {
+ throw new IllegalArgumentException("Display name is required and cannot be empty.");
+ }
+ validateExpiresAtAndRoomType(expiresAt = builder.expiresAt, type = builder.type);
+ if ((expireAfterUse = builder.expireAfterUse) != null && type == RoomType.INSTANT) {
+ throw new IllegalStateException("expireAfterUse is not applicable to "+type+" rooms.");
+ }
+ metadata = builder.metadata;
+ isAvailable = builder.isAvailable;
+ recordingOptions = builder.recordingOptions;
+ initialJoinOptions = builder.initialJoinOptions;
+ callbackUrls = builder.callbackUrls;
+ availableFeatures = builder.availableFeatures;
+ themeId = builder.themeId;
+ joinApprovalLevel = builder.joinApprovalLevel;
+ uiSettings = builder.uiSettings;
+ }
+
+ static void validateExpiresAtAndRoomType(Instant expiresAt, RoomType type) {
+ if (type == RoomType.INSTANT && expiresAt != null) {
+ throw new IllegalStateException("Expiration time should not be specified for "+type+" rooms.");
+ }
+ else if (type == RoomType.LONG_TERM && expiresAt == null) {
+ throw new IllegalStateException("Expiration time must be specified for "+type+" rooms.");
+ }
+ if (expiresAt != null && expiresAt.isBefore(Instant.now().plusSeconds(600))) {
+ throw new IllegalArgumentException("Expiration time should be more than 10 minutes from now.");
+ }
+ }
+
+ /**
+ * Name of the meeting room.
+ *
+ * @return The display name.
+ */
+ @JsonProperty("display_name")
+ public String getDisplayName() {
+ return displayName;
+ }
+
+ /**
+ * Free text that can be attached to a room.
+ * This will be passed in the form of a header in events related to this room.
+ *
+ * @return The room description / metadata.
+ */
+ @JsonProperty("metadata")
+ public String getMetadata() {
+ return metadata;
+ }
+
+ /**
+ * The meeting code, which is used in the URL to join the meeting.
+ *
+ * @return The share code for this meeting.
+ */
+ @JsonProperty("meeting_code")
+ public String getMeetingCode() {
+ return meetingCode;
+ }
+
+ /**
+ * Type of room.
+ *
+ * @return The room type, as an enum.
+ */
+ @JsonProperty("type")
+ public RoomType getType() {
+ return type;
+ }
+
+ /**
+ * Once a room becomes unavailable, no new sessions can be created under it.
+ *
+ * @return Whether the room is available, or {@code null} if unknown.
+ */
+ @JsonProperty("is_available")
+ public Boolean getIsAvailable() {
+ return isAvailable;
+ }
+
+ /**
+ * Close the room after a session ends. Only relevant for long_term rooms.
+ *
+ * @return {@code true} if the room will close after end of session, or {@code null} if unknown / not applicable.
+ */
+ @JsonProperty("expire_after_use")
+ public Boolean getExpireAfterUse() {
+ return expireAfterUse;
+ }
+
+ /**
+ * Options for recording.
+ *
+ * @return The recording options.
+ */
+ @JsonProperty("recording_options")
+ public RecordingOptions getRecordingOptions() {
+ return recordingOptions;
+ }
+
+ /**
+ * Options for when a participant joins a meeting.
+ *
+ * @return The initial joining options.
+ */
+ @JsonProperty("initial_join_options")
+ public InitialJoinOptions getInitialJoinOptions() {
+ return initialJoinOptions;
+ }
+
+ /**
+ * Settings for the user interface of this meeting.
+ *
+ * @return The UI settings object.
+ */
+ @JsonProperty("ui_settings")
+ public UISettings getUiSettings() {
+ return uiSettings;
+ }
+
+ /**
+ * The callback URLs for this meeting.
+ *
+ * @return The CallbackUrls object.
+ */
+ @JsonProperty("callback_urls")
+ public CallbackUrls getCallbackUrls() {
+ return callbackUrls;
+ }
+
+ /**
+ * The available features for this meeting.
+ *
+ * @return The AvailableFeatures object.
+ */
+ @JsonProperty("available_features")
+ public AvailableFeatures getAvailableFeatures() {
+ return availableFeatures;
+ }
+
+ /**
+ * ID of the theme for this room.
+ *
+ * @return The theme ID.
+ */
+ @JsonProperty("theme_id")
+ public UUID getThemeId() {
+ return themeId;
+ }
+
+ /**
+ * The level of approval needed to join the meeting in the room.
+ *
+ * @return The approval level, as an enum.
+ */
+ @JsonProperty("join_approval_level")
+ public JoinApprovalLevel getJoinApprovalLevel() {
+ return joinApprovalLevel;
+ }
+
+ /**
+ * The time for when the room was created, expressed in ISO 8601 format.
+ *
+ * @return The room creation time.
+ */
+ @JsonProperty("created_at")
+ public Instant getCreatedAt() {
+ return createdAt;
+ }
+
+ /**
+ * The time for when the room will be expired, expressed in ISO 8601 format.
+ *
+ * @return The room expiration time.
+ */
+ @JsonProperty("expires_at")
+ public Instant getExpiresAt() {
+ return expiresAt;
+ }
+
+ /**
+ * Identifier of the meeting room.
+ *
+ * @return The room ID.
+ */
+ @JsonProperty("id")
+ public UUID getId() {
+ return id;
+ }
+
+ /**
+ * Useful links.
+ *
+ * @return The nested links object.
+ */
+ @JsonProperty("_links")
+ public RoomLinks getLinks() {
+ return links;
+ }
+
+ /**
+ * Updates (hydrates) this object's fields from additional JSON data.
+ *
+ * @param json The JSON payload.
+ */
+ public void updateFromJson(String json) {
+ try {
+ ObjectMapper mapper = new ObjectMapper();
+ mapper.registerModule(new JavaTimeModule());
+ mapper.readerForUpdating(this).readValue(json, getClass());
+ }
+ catch (IOException ex) {
+ throw new VonageResponseParseException("Failed to update "+getClass().getSimpleName()+" from json.", ex);
+ }
+ }
+
+ /**
+ * Creates an instance of this class from a JSON payload.
+ *
+ * @param json The JSON string to parse.
+ * @return An instance of this class with the fields populated, if present.
+ */
+ public static MeetingRoom fromJson(String json) {
+ try {
+ ObjectMapper mapper = new ObjectMapper();
+ mapper.registerModule(new JavaTimeModule());
+ return mapper.readValue(json, MeetingRoom.class);
+ }
+ catch (IOException ex) {
+ throw new VonageResponseParseException("Failed to produce MeetingRoom from json.", ex);
+ }
+ }
+
+ /**
+ * Generates a JSON payload from this request.
+ *
+ * @return JSON representation of this MeetingRoom object.
+ */
+ public String toJson() {
+ try {
+ ObjectMapper mapper = new ObjectMapper();
+ mapper.registerModule(new JavaTimeModule());
+ mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
+ return mapper.writeValueAsString(this);
+ }
+ catch (JsonProcessingException jpe) {
+ throw new VonageUnexpectedException("Failed to produce JSON from "+getClass().getSimpleName()+" object.", jpe);
+ }
+ }
+
+ /**
+ * Entry point for constructing an instance of this class.
+ *
+ * @param displayName Name of the meeting room.
+ *
+ * @return A new Builder.
+ */
+ public static Builder builder(String displayName) {
+ return new Builder(displayName);
+ }
+
+ public static class Builder {
+ private final String displayName;
+ private UUID themeId;
+ private String metadata;
+ private Instant expiresAt;
+ private Boolean isAvailable, expireAfterUse;
+ private RoomType type;
+ private JoinApprovalLevel joinApprovalLevel;
+ private RecordingOptions recordingOptions;
+ private InitialJoinOptions initialJoinOptions;
+ private UISettings uiSettings;
+ private CallbackUrls callbackUrls;
+ private AvailableFeatures availableFeatures;
+
+ Builder(String displayName) {
+ this.displayName = displayName;
+ }
+
+ /**
+ * Free text that can be attached to a room. This will be passed in the form of a header
+ * in events related to this room.
+ *
+ * @param metadata Additional text / data to be associated with this room.
+ *
+ * @return This builder.
+ */
+ public Builder metadata(String metadata) {
+ this.metadata = metadata;
+ return this;
+ }
+
+ /**
+ * Type of room.
+ *
+ * @param type The room type.
+ *
+ * @return This builder.
+ */
+ public Builder type(RoomType type) {
+ this.type = type;
+ return this;
+ }
+
+ /**
+ * Once a room becomes unavailable, no new sessions can be created under it.
+ *
+ * @param isAvailable Whether the room is available.
+ *
+ * @return This builder.
+ */
+ public Builder isAvailable(boolean isAvailable) {
+ this.isAvailable = isAvailable;
+ return this;
+ }
+
+ /**
+ * Close the room after a session ends. Only relevant for long term rooms.
+ *
+ * @param expireAfterUse Whether the room should close after use.
+ *
+ * @return This builder.
+ */
+ public Builder expireAfterUse(boolean expireAfterUse) {
+ this.expireAfterUse = expireAfterUse;
+ return this;
+ }
+
+ /**
+ * Options for recording.
+ *
+ * @param recordingOptions The recording options.
+ *
+ * @return This builder.
+ */
+ public Builder recordingOptions(RecordingOptions recordingOptions) {
+ this.recordingOptions = recordingOptions;
+ return this;
+ }
+
+ /**
+ * Options for when a participant joins a meeting.
+ *
+ * @param initialJoinOptions The initial join options for each participant.
+ *
+ * @return This builder.
+ */
+ public Builder initialJoinOptions(InitialJoinOptions initialJoinOptions) {
+ this.initialJoinOptions = initialJoinOptions;
+ return this;
+ }
+
+ /**
+ * Settings for the user interface of this meeting.
+ *
+ * @param uiSettings The user interface settings.
+ *
+ * @return This builder.
+ */
+ public Builder uiSettings(UISettings uiSettings) {
+ this.uiSettings = uiSettings;
+ return this;
+ }
+
+ /**
+ * Callback URLs for this meeting.
+ *
+ * @param callbackUrls The additional URLs for this meeting.
+ *
+ * @return This builder.
+ */
+ public Builder callbackUrls(CallbackUrls callbackUrls) {
+ this.callbackUrls = callbackUrls;
+ return this;
+ }
+
+ /**
+ * Available features for this meeting.
+ *
+ * @param availableFeatures The features available for this room.
+ *
+ * @return This builder.
+ */
+ public Builder availableFeatures(AvailableFeatures availableFeatures) {
+ this.availableFeatures = availableFeatures;
+ return this;
+ }
+
+ /**
+ * ID of the theme for this room.
+ *
+ * @param themeId Unique theme identifier.
+ *
+ * @return This builder.
+ */
+ public Builder themeId(UUID themeId) {
+ this.themeId = themeId;
+ return this;
+ }
+
+ /**
+ * Level of approval needed to join the meeting in the room.
+ *
+ * @param joinApprovalLevel The permission setting for joining this room.
+ *
+ * @return This builder.
+ */
+ public Builder joinApprovalLevel(JoinApprovalLevel joinApprovalLevel) {
+ this.joinApprovalLevel = joinApprovalLevel;
+ return this;
+ }
+
+ /**
+ * NOTE: This parameter is REQUIRED if the room type is {@link RoomType#LONG_TERM},
+ * but should not be present if the room type is {@link RoomType#INSTANT}.
+ *
+ * @param expiresAt The time for when the room will expire, expressed in ISO 8601 format.
+ *
+ * @return This builder.
+ */
+ public Builder expiresAt(Instant expiresAt) {
+ this.expiresAt = expiresAt.truncatedTo(ChronoUnit.MILLIS);
+ return this;
+ }
+
+
+ /**
+ * Builds the {@linkplain MeetingRoom}.
+ *
+ * @return An instance of MeetingRoom, populated with all fields from this builder.
+ */
+ public MeetingRoom build() {
+ return new MeetingRoom(this);
+ }
+ }
+}
diff --git a/src/main/java/com/vonage/client/meetings/MeetingsClient.java b/src/main/java/com/vonage/client/meetings/MeetingsClient.java
new file mode 100644
index 000000000..149561896
--- /dev/null
+++ b/src/main/java/com/vonage/client/meetings/MeetingsClient.java
@@ -0,0 +1,430 @@
+/*
+ * Copyright 2023 Vonage
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.vonage.client.meetings;
+
+import com.vonage.client.*;
+import com.vonage.client.common.HalPageResponse;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.StatusLine;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.methods.RequestBuilder;
+import org.apache.http.client.utils.URLEncodedUtils;
+import org.apache.http.entity.mime.MultipartEntityBuilder;
+import java.io.IOException;
+import java.net.URI;
+import java.nio.charset.Charset;
+import java.nio.file.Path;
+import java.util.*;
+
+public class MeetingsClient {
+ HttpClient httpClient;
+ final ListRoomsEndpoint listRooms;
+ final GetRoomEndpoint getRoom;
+ final CreateRoomEndpoint createRoom;
+ final UpdateRoomEndpoint updateRoom;
+ final SearchThemeRoomsEndpoint searchThemeRooms;
+ final ListThemesEndpoint listThemes;
+ final GetThemeEndpoint getTheme;
+ final CreateThemeEndpoint createTheme;
+ final UpdateThemeEndpoint updateTheme;
+ final DeleteThemeEndpoint deleteTheme;
+ final ListRecordingsEndpoint listRecordings;
+ final GetRecordingEndpoint getRecording;
+ final DeleteRecordingEndpoint deleteRecording;
+ final ListDialNumbersEndpoint listDialNumbers;
+ final UpdateApplicationEndpoint updateApplication;
+ final FinalizeLogosEndpoint finalizeLogos;
+ final GetLogoUploadUrlsEndpoint getLogoUploadUrls;
+
+ /**
+ * Constructor.
+ *
+ * @param httpWrapper (REQUIRED) shared HTTP wrapper object used for making REST calls.
+ */
+ public MeetingsClient(HttpWrapper httpWrapper) {
+ httpClient = httpWrapper.getHttpClient();
+ listRooms = new ListRoomsEndpoint(httpWrapper);
+ getRoom = new GetRoomEndpoint(httpWrapper);
+ createRoom = new CreateRoomEndpoint(httpWrapper);
+ updateRoom = new UpdateRoomEndpoint(httpWrapper);
+ searchThemeRooms = new SearchThemeRoomsEndpoint(httpWrapper);
+ listThemes = new ListThemesEndpoint(httpWrapper);
+ getTheme = new GetThemeEndpoint(httpWrapper);
+ createTheme = new CreateThemeEndpoint(httpWrapper);
+ updateTheme = new UpdateThemeEndpoint(httpWrapper);
+ deleteTheme = new DeleteThemeEndpoint(httpWrapper);
+ listRecordings = new ListRecordingsEndpoint(httpWrapper);
+ getRecording = new GetRecordingEndpoint(httpWrapper);
+ deleteRecording = new DeleteRecordingEndpoint(httpWrapper);
+ listDialNumbers = new ListDialNumbersEndpoint(httpWrapper);
+ updateApplication = new UpdateApplicationEndpoint(httpWrapper);
+ finalizeLogos = new FinalizeLogosEndpoint(httpWrapper);
+ getLogoUploadUrls = new GetLogoUploadUrlsEndpoint(httpWrapper);
+ }
+
+ static UUID validateThemeId(UUID themeId) {
+ return Objects.requireNonNull(themeId, "Theme ID is required.");
+ }
+
+ static UUID validateRoomId(UUID roomId) {
+ return Objects.requireNonNull(roomId, "Room ID is required.");
+ }
+
+ static UUID validateRecordingId(UUID recordingId) {
+ return Objects.requireNonNull(recordingId, "Recording ID is required.");
+ }
+
+ static String validateSessionId(String sessionId) {
+ if (sessionId == null || sessionId.trim().isEmpty()) {
+ throw new IllegalArgumentException("Session ID cannot be null or empty.");
+ }
+ return sessionId;
+ }
+
+ private int parseNextFromHalResponse(HalPageResponse response) {
+ final URI nextUrl = response.getLinks().getNextUrl();
+ return URLEncodedUtils.parse(nextUrl, Charset.defaultCharset())
+ .stream().filter(nvp -> "start_id".equals(nvp.getName()))
+ .findFirst().map(nvp -> Integer.parseInt(nvp.getValue()))
+ .orElseThrow(() -> new VonageClientException("Couldn't navigate to next page: "+nextUrl));
+
+ }
+
+ private List getAllRoomsFromResponseRecursively(
+ AbstractMethod endpoint, ListRoomsRequest initialRequest) {
+
+ final int initialPageSize = initialRequest.pageSize != null ? initialRequest.pageSize : 1000;
+ ListRoomsRequest request = new ListRoomsRequest(
+ initialRequest.startId, initialRequest.endId, initialPageSize, initialRequest.themeId
+ );
+ ListRoomsResponse response = endpoint.execute(request);
+
+ if (response.getTotalItems() < initialPageSize) {
+ return response.getMeetingRooms();
+ }
+ else {
+ List rooms = new ArrayList<>(response.getMeetingRooms());
+ do {
+ request = new ListRoomsRequest(
+ parseNextFromHalResponse(response), null, initialPageSize, request.themeId
+ );
+ response = endpoint.execute(request);
+ rooms.addAll(response.getMeetingRooms());
+ }
+ while (response.getTotalItems() < initialPageSize);
+ return rooms;
+ }
+ }
+
+ /**
+ * Get all listed rooms in the application.
+ *
+ * @return The list of all meeting rooms.
+ *
+ * @throws MeetingsResponseException If there is an error encountered when processing the request.
+ */
+ public List listRooms() {
+ return getAllRoomsFromResponseRecursively(listRooms,
+ new ListRoomsRequest(null, null, null, null)
+ );
+ }
+
+ /**
+ * Get details of an existing room.
+ *
+ * @param roomId ID of the room to retrieve.
+ *
+ * @return The meeting room associated with the ID.
+ *
+ * @throws MeetingsResponseException If there is an error encountered when processing the request.
+ */
+ public MeetingRoom getRoom(UUID roomId) {
+ return getRoom.execute(validateRoomId(roomId));
+ }
+
+ /**
+ * Create a new room.
+ *
+ * @param room Properties of the meeting room.
+ *
+ * @return Details of the created meeting room.
+ *
+ * @throws MeetingsResponseException If there is an error encountered when processing the request.
+ */
+ public MeetingRoom createRoom(MeetingRoom room) {
+ return createRoom.execute(Objects.requireNonNull(room, "Meeting room is required."));
+ }
+
+ /**
+ * Update an existing room.
+ *
+ * @param roomId ID of the meeting room to be updated.
+ * @param roomUpdate Properties of the meeting room to change.
+ *
+ * @return Details of the updated meeting room.
+ *
+ * @throws MeetingsResponseException If there is an error encountered when processing the request.
+ */
+ public MeetingRoom updateRoom(UUID roomId, UpdateRoomRequest roomUpdate) {
+ Objects.requireNonNull(roomUpdate, "Room update request properties is required.");
+ roomUpdate.roomId = validateRoomId(roomId);
+ return updateRoom.execute(roomUpdate);
+ }
+
+ /**
+ * Get rooms that are associated with a theme ID.
+ *
+ * @param themeId The theme ID to filter by.
+ *
+ * @return The list of rooms which use the theme.
+ *
+ * @throws MeetingsResponseException If there is an error encountered when processing the request.
+ */
+ public List searchRoomsByTheme(UUID themeId) {
+ return getAllRoomsFromResponseRecursively(searchThemeRooms,
+ new ListRoomsRequest(null, null, null, validateThemeId(themeId))
+ );
+ }
+
+ /**
+ * Get all application themes.
+ *
+ * @return The list of themes.
+ *
+ * @throws MeetingsResponseException If there is an error encountered when processing the request.
+ */
+ public List listThemes() {
+ return listThemes.execute(null);
+ }
+
+ /**
+ * Retrieve details of a theme by ID.
+ *
+ * @param themeId The theme ID.
+ *
+ * @return The theme associated with the ID.
+ *
+ * @throws MeetingsResponseException If there is an error encountered when processing the request.
+ */
+ public Theme getTheme(UUID themeId) {
+ return getTheme.execute(validateThemeId(themeId));
+ }
+
+ /**
+ * Create a new theme.
+ *
+ * @param theme The partial theme properties.
+ *
+ * @return The full created theme details.
+ *
+ * @throws MeetingsResponseException If there is an error encountered when processing the request.
+ */
+ public Theme createTheme(Theme theme) {
+ Objects.requireNonNull(theme, "Theme creation properties are required.");
+ Objects.requireNonNull(theme.getBrandText(), "Brand text is required.");
+ Objects.requireNonNull(theme.getMainColor(), "Main color is required.");
+ return createTheme.execute(theme);
+ }
+
+ /**
+ * Update an existing theme.
+ *
+ * @param themeId ID of the theme to update.
+ * @param theme The partial theme properties to update.
+ *
+ * @return The full updated theme details.
+ *
+ * @throws MeetingsResponseException If there is an error encountered when processing the request.
+ */
+ public Theme updateTheme(UUID themeId, Theme theme) {
+ Objects.requireNonNull(theme, "Theme update properties are required.");
+ theme.themeId = validateThemeId(themeId);
+ return updateTheme.execute(theme);
+ }
+
+ /**
+ * Delete a theme by its ID.
+ *
+ * @param themeId ID of the theme to delete.
+ * @param force Whether to delete the theme even if theme is used by rooms or as application default theme.
+ *
+ * @throws MeetingsResponseException If there is an error encountered when processing the request.
+ */
+ public void deleteTheme(UUID themeId, boolean force) {
+ deleteTheme.execute(new DeleteThemeRequest(validateThemeId(themeId), force));
+ }
+
+ /**
+ * Get recordings of a meeting session.
+ *
+ * @param sessionId The session ID to filter recordings by.
+ *
+ * @return The list of recordings for the session.
+ *
+ * @throws MeetingsResponseException If there is an error encountered when processing the request.
+ */
+ public List listRecordings(String sessionId) {
+ ListRecordingsResponse response = listRecordings.execute(validateSessionId(sessionId));
+ List recordings = response.getRecordings();
+ return recordings != null ? recordings : Collections.emptyList();
+ }
+
+ /**
+ * Get details of a recording.
+ *
+ * @param recordingId ID of the recording to retrieve.
+ *
+ * @return The recording properties.
+ *
+ * @throws MeetingsResponseException If there is an error encountered when processing the request.
+ */
+ public Recording getRecording(UUID recordingId) {
+ return getRecording.execute(validateRecordingId(recordingId));
+ }
+
+ /**
+ * Delete a recording.
+ *
+ * @param recordingId ID of the recording to delete.
+ *
+ * @throws MeetingsResponseException If there is an error encountered when processing the request.
+ */
+ public void deleteRecording(UUID recordingId) {
+ deleteRecording.execute(validateRecordingId(recordingId));
+ }
+
+ /**
+ * Get numbers that can be used to dial into a meeting.
+ *
+ * @return The list of dial-in numbers, along with their country code.
+ *
+ * @throws MeetingsResponseException If there is an error encountered when processing the request.
+ */
+ public List listDialNumbers() {
+ return listDialNumbers.execute(null);
+ }
+
+ /**
+ * Update an existing application.
+ *
+ * @param updateRequest Properties of the application to update.
+ *
+ * @return The updated application details.
+ *
+ * @throws MeetingsResponseException If there is an error encountered when processing the request.
+ */
+ public Application updateApplication(UpdateApplicationRequest updateRequest) {
+ return updateApplication.execute(Objects.requireNonNull(
+ updateRequest, "Application update properties are required.")
+ );
+ }
+
+ /**
+ * Change logos to be permanent for a given theme.
+ *
+ * @param themeId The theme ID containing the logos.
+ * @param keys List of temporary theme's logo keys to make permanent
+ */
+ void finalizeLogos(UUID themeId, List keys) {
+ if (keys == null || keys.isEmpty()) {
+ throw new IllegalArgumentException("Logo keys are required.");
+ }
+ finalizeLogos.execute(new FinalizeLogosRequest(validateThemeId(themeId), keys));
+ }
+
+ /**
+ * Get URLs that can be used to upload logos for a theme via a POST.
+ *
+ * @return List of URLs and respective credentials / tokens needed for uploading logos to them.
+ */
+ List listLogoUploadUrls() {
+ return getLogoUploadUrls.execute(null);
+ }
+
+ /**
+ * Finds the appropriate response object from {@linkplain #listLogoUploadUrls()} for the given logo type.
+ *
+ * @param logoType The logo type to get details for.
+ * @return The URL and credential fields for uploading the logo.
+ */
+ LogoUploadsUrlResponse getUploadDetailsForLogoType(LogoType logoType) {
+ return listLogoUploadUrls().stream()
+ .filter(r -> logoType.equals(r.getFields().getLogoType()))
+ .findFirst()
+ .orElseThrow(() -> new IllegalArgumentException("Logo type "+logoType+" is unavailable."));
+ }
+
+ /**
+ * Uploads a logo to the cloud so that it can be used in themes.
+ *
+ * @param logoFile Absolute path to the image.
+ * @param details Credentials, logo key and URL to facilitate the upload request.
+ */
+ void uploadLogo(Path logoFile, LogoUploadsUrlResponse details) {
+ try {
+ LogoUploadsUrlResponse.Fields fields = details.getFields();
+ HttpEntity entity = MultipartEntityBuilder.create()
+ .addTextBody("Content-Type", fields.getContentType().toString())
+ .addTextBody("key", fields.getKey())
+ .addTextBody("logoType", fields.getLogoType().toString())
+ .addTextBody("bucket", fields.getBucket())
+ .addTextBody("X-Amz-Algorithm", fields.getAmzAlgorithm())
+ .addTextBody("X-Amz-Credential", fields.getAmzCredential())
+ .addTextBody("X-Amz-Date", fields.getAmzDate())
+ .addTextBody("X-Amz-Security-Token", fields.getAmzSecurityToken())
+ .addTextBody("Policy", fields.getPolicy())
+ .addTextBody("X-Amz-Signature", fields.getAmzSignature())
+ .addBinaryBody("file", logoFile.toFile())
+ .build();
+ HttpResponse response = httpClient.execute(
+ RequestBuilder.post(details.getUrl()).setEntity(entity).build()
+ );
+ StatusLine status = response.getStatusLine();
+ int statusCode = status.getStatusCode();
+ if (statusCode != 204) {
+ MeetingsResponseException mrx = new MeetingsResponseException(
+ "Logo upload failed ("+statusCode+"): "+status.getReasonPhrase()
+ );
+ mrx.setStatusCode(statusCode);
+ throw mrx;
+ }
+ }
+ catch (IOException ex) {
+ throw new VonageUnexpectedException(ex);
+ }
+ }
+
+ /**
+ * Upload a logo image and associates it with a theme.
+ *
+ * @param themeId ID of the theme which the logo will be associated with.
+ * @param logoType The logo type to upload.
+ * @param pngFile Absolute path to the logo image. For restrictions, refer to
+ *
+ * the documentation. Generally, the image must be a PNG under 1MB, square, under 300x300 pixels and
+ * have a transparent background.
+ *
+ * @throws MeetingsResponseException If there is an error encountered when processing the request.
+ */
+ public void updateThemeLogo(UUID themeId, LogoType logoType, Path pngFile) {
+ LogoUploadsUrlResponse target = getUploadDetailsForLogoType(
+ Objects.requireNonNull(logoType, "Logo type cannot be null.")
+ );
+ uploadLogo(Objects.requireNonNull(pngFile, "Image file cannot be null."), target);
+ finalizeLogos(themeId, Collections.singletonList(target.getFields().getKey()));
+ }
+}
diff --git a/src/main/java/com/vonage/client/meetings/MeetingsEventCallback.java b/src/main/java/com/vonage/client/meetings/MeetingsEventCallback.java
new file mode 100644
index 000000000..02caa16a5
--- /dev/null
+++ b/src/main/java/com/vonage/client/meetings/MeetingsEventCallback.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright 2023 Vonage
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.vonage.client.meetings;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
+import com.vonage.client.VonageResponseParseException;
+import java.io.IOException;
+import java.net.URI;
+import java.time.Instant;
+import java.util.UUID;
+
+/**
+ * Represents a deserialized callback response webhook for event updates from the Meetings API.
+ */
+public class MeetingsEventCallback {
+ private EventType event;
+ private String sessionId, participantName, participantType;
+ private UUID roomId, recordingId, participantId;
+ private RoomType roomType;
+ private Instant createdAt, startedAt, expiresAt, endedAt;
+ private Integer duration;
+ private URI recordingUrl;
+ private Boolean isHost;
+
+ protected MeetingsEventCallback() {
+ }
+
+ /**
+ * Type of event represented by this object.
+ *
+ * @return The event type as an enum.
+ */
+ @JsonProperty("event")
+ public EventType getEvent() {
+ return event;
+ }
+
+ /**
+ * Corresponds to the underlying Video API session ID.
+ *
+ * @return The session ID, or {@code null} if not applicable.
+ */
+ @JsonProperty("session_id")
+ public String getSessionId() {
+ return sessionId;
+ }
+
+ /**
+ * The participant name.
+ *
+ * @return Display name of the participant, or {@code null} if not applicable.
+ */
+ @JsonProperty("name")
+ public String getParticipantName() {
+ return participantName;
+ }
+
+ /**
+ * The participant type (e.g. "Guest").
+ *
+ * @return Type of participant, or {@code null} if not applicable.
+ */
+ @JsonProperty("type")
+ public String getParticipantType() {
+ return participantType;
+ }
+
+ /**
+ * Meeting room's ID.
+ *
+ * @return The room ID, or {@code null} if not applicable.
+ */
+ @JsonProperty("room_id")
+ public UUID getRoomId() {
+ return roomId;
+ }
+
+ /**
+ * Unique recording ID for the meeting.
+ *
+ * @return The recording ID, or {@code null} if not applicable.
+ */
+ @JsonProperty("recording_id")
+ public UUID getRecordingId() {
+ return recordingId;
+ }
+
+ /**
+ * Unique identifier for the participant.
+ *
+ * @return The participant ID, or {@code null} if not applicable.
+ */
+ @JsonProperty("participant_id")
+ public UUID getParticipantId() {
+ return participantId;
+ }
+
+ /**
+ * The type of meeting room.
+ *
+ * @return The meeting room type as an enum, or {@code null} if not applicable.
+ */
+ @JsonProperty("room_type")
+ public RoomType getRoomType() {
+ return roomType;
+ }
+
+ /**
+ * The date-time when the room will expire, expressed in ISO 8601 format.
+ *
+ * @return The room expiration timestamp, or {@code null} if not applicable.
+ */
+ @JsonProperty("expires_at")
+ public Instant getExpiresAt() {
+ return expiresAt;
+ }
+
+ /**
+ * The date-time when the room was created, expressed in ISO 8601 format.
+ *
+ * @return The room creation timestamp, or {@code null} if not applicable.
+ */
+ @JsonProperty("created_at")
+ public Instant getCreatedAt() {
+ return createdAt;
+ }
+
+ /**
+ * The date-time when the session started, expressed in ISO 8601 format.
+ *
+ * @return , or {@code null} if not applicable.
+ */
+ @JsonProperty("started_at")
+ public Instant getStartedAt() {
+ return startedAt;
+ }
+
+ /**
+ * The date-time the session or recording ended, expressed in ISO 8601 format.
+ *
+ * @return The end timestamp, or {@code null} if not applicable.
+ */
+ @JsonProperty("ended_at")
+ public Instant getEndedAt() {
+ return endedAt;
+ }
+
+ /**
+ * Duration of the recording in seconds.
+ *
+ * @return The duration in seconds, or {@code null} if not applicable.
+ */
+ @JsonProperty("duration")
+ public Integer getDuration() {
+ return duration;
+ }
+
+ /**
+ * URL of the uploaded recording.
+ *
+ * @return The recording URL, or {@code null} if not applicable.
+ */
+ @JsonProperty("url")
+ public URI getRecordingUrl() {
+ return recordingUrl;
+ }
+
+ /**
+ * Indicates if this participant is the session's host.
+ *
+ * @return Whether the participant is the session host, or {@code null} if not applicable.
+ */
+ @JsonProperty("is_host")
+ public Boolean getIsHost() {
+ return isHost;
+ }
+
+ /**
+ * Constructs an instance of this class from a JSON payload.
+ *
+ * @param json The webhook response JSON string.
+ *
+ * @return The deserialized webhook response object.
+ * @throws VonageResponseParseException If the response could not be deserialized.
+ */
+ public static MeetingsEventCallback fromJson(String json) {
+ try {
+ ObjectMapper mapper = new ObjectMapper();
+ mapper.registerModule(new JavaTimeModule());
+ return mapper.readValue(json, MeetingsEventCallback.class);
+ }
+ catch (IOException ex) {
+ throw new VonageResponseParseException("Failed to produce MeetingsEventCallback from json.", ex);
+ }
+ }
+}
diff --git a/src/main/java/com/vonage/client/meetings/MeetingsResponseException.java b/src/main/java/com/vonage/client/meetings/MeetingsResponseException.java
new file mode 100644
index 000000000..c59a752b2
--- /dev/null
+++ b/src/main/java/com/vonage/client/meetings/MeetingsResponseException.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2023 Vonage
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.vonage.client.meetings;
+
+import com.fasterxml.jackson.annotation.*;
+import com.vonage.client.VonageApiResponseException;
+import org.apache.http.HttpResponse;
+import java.io.IOException;
+
+/**
+ * Response returned when an error is encountered (i.e. the API returns a non-2xx status code).
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public final class MeetingsResponseException extends VonageApiResponseException {
+ @JsonProperty("status") private Integer status;
+ @JsonProperty("name") private String name;
+ @JsonProperty("message") private String message;
+
+ public MeetingsResponseException() {
+ super();
+ }
+
+ MeetingsResponseException(String message) {
+ super(message);
+ }
+
+ void setStatusCode(Integer status) {
+ this.status = statusCode = status;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ @JsonGetter("message")
+ @Override
+ public String getMessage() {
+ return message != null ? message : super.getMessage();
+ }
+
+ private static MeetingsResponseException setStatus(MeetingsResponseException ex) {
+ if (ex.status == null || ex.status < 100) {
+ ex.setStatusCode(ex.statusCode);
+ }
+ else if (ex.statusCode < 100) {
+ ex.setStatusCode(ex.status);
+ }
+ return ex;
+ }
+
+ /**
+ * Creates an instance of this class from a JSON payload.
+ *
+ * @param json The JSON string to parse.
+ * @return An instance of this class with all known fields populated from the JSON payload, if present.
+ */
+ @JsonCreator
+ public static MeetingsResponseException fromJson(String json) {
+ return setStatus(fromJson(MeetingsResponseException.class, json));
+ }
+
+ static MeetingsResponseException fromHttpResponse(HttpResponse response) throws IOException {
+ return setStatus(fromHttpResponse(MeetingsResponseException.class, response));
+ }
+}
diff --git a/src/main/java/com/vonage/client/meetings/MicrophoneState.java b/src/main/java/com/vonage/client/meetings/MicrophoneState.java
new file mode 100644
index 000000000..6d5bf4821
--- /dev/null
+++ b/src/main/java/com/vonage/client/meetings/MicrophoneState.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2023 Vonage
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.vonage.client.meetings;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonValue;
+
+/**
+ * Set the default microphone option for users in the pre-join screen of this room.
+ */
+public enum MicrophoneState {
+ ON,
+ OFF,
+ DEFAULT;
+
+ @JsonCreator
+ public static MicrophoneState fromString(String value) {
+ try {
+ return valueOf(value.toUpperCase());
+ }
+ catch (NullPointerException | IllegalArgumentException ex) {
+ return null;
+ }
+ }
+
+ @JsonValue
+ @Override
+ public String toString() {
+ return name().toLowerCase();
+ }
+}
diff --git a/src/main/java/com/vonage/client/meetings/Recording.java b/src/main/java/com/vonage/client/meetings/Recording.java
new file mode 100644
index 000000000..7fdc26ac3
--- /dev/null
+++ b/src/main/java/com/vonage/client/meetings/Recording.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2023 Vonage
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.vonage.client.meetings;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
+import com.vonage.client.VonageResponseParseException;
+import java.io.IOException;
+import java.net.URI;
+import java.time.Instant;
+import java.util.UUID;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class Recording {
+ private UUID id;
+ private String sessionId;
+ private Instant startedAt, endedAt;
+ private RecordingStatus status;
+ @JsonProperty("_links") private RecordingLinks links;
+
+ protected Recording() {
+ }
+
+ /**
+ * Identifier of the recording.
+ *
+ * @return The recording ID.
+ */
+ @JsonProperty("id")
+ public UUID getId() {
+ return id;
+ }
+
+ /**
+ * Corresponds to the underlying Video API session ID.
+ *
+ * @return The video session ID.
+ */
+ @JsonProperty("session_id")
+ public String getSessionId() {
+ return sessionId;
+ }
+
+ /**
+ * Recording start time, in ISO 8601 format.
+ *
+ * @return The recording start time.
+ */
+ @JsonProperty("started_at")
+ public Instant getStartedAt() {
+ return startedAt;
+ }
+
+ /**
+ * Recording end time, in ISO 8601 format.
+ *
+ * @return The recording end time.
+ */
+ @JsonProperty("ended_at")
+ public Instant getEndedAt() {
+ return endedAt;
+ }
+
+ /**
+ * Status of the recording.
+ *
+ * @return The recording status, as an enum.
+ */
+ @JsonProperty("status")
+ public RecordingStatus getStatus() {
+ return status;
+ }
+
+ /**
+ * The URL property of the {@code _links} response.
+ *
+ * @return The recording URL or {@code null} if not available.
+ */
+ @JsonIgnore
+ public URI getUrl() {
+ return links != null ? links.getUrl() : null;
+ }
+
+ /**
+ * Creates an instance of this class from a JSON payload.
+ *
+ * @param json The JSON string to parse.
+ * @return An instance of this class with the fields populated, if present.
+ */
+ public static Recording fromJson(String json) {
+ try {
+ ObjectMapper mapper = new ObjectMapper();
+ mapper.registerModule(new JavaTimeModule());
+ return mapper.readValue(json, Recording.class);
+ }
+ catch (IOException ex) {
+ throw new VonageResponseParseException("Failed to produce Recording from json.", ex);
+ }
+ }
+}
diff --git a/src/main/java/com/vonage/client/meetings/RecordingLinks.java b/src/main/java/com/vonage/client/meetings/RecordingLinks.java
new file mode 100644
index 000000000..3f38c6d8c
--- /dev/null
+++ b/src/main/java/com/vonage/client/meetings/RecordingLinks.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2023 Vonage
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.vonage.client.meetings;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.net.URI;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+class RecordingLinks {
+ private UrlContainer url;
+
+ /**
+ * Recording URL.
+ *
+ * @return The recording URL.
+ */
+ @JsonProperty("url")
+ public URI getUrl() {
+ return url != null ? url.getHref() : null;
+ }
+}
diff --git a/src/main/java/com/vonage/client/meetings/RecordingOptions.java b/src/main/java/com/vonage/client/meetings/RecordingOptions.java
new file mode 100644
index 000000000..169252d44
--- /dev/null
+++ b/src/main/java/com/vonage/client/meetings/RecordingOptions.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2023 Vonage
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.vonage.client.meetings;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+@JsonInclude(value = JsonInclude.Include.NON_NULL)
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class RecordingOptions {
+ private Boolean autoRecord, recordOnlyOwner;
+
+ protected RecordingOptions() {
+ }
+
+ RecordingOptions(Builder builder) {
+ autoRecord = builder.autoRecord;
+ recordOnlyOwner = builder.recordOnlyOwner;
+ }
+
+ /**
+ * Automatically record all sessions in this room. Recording cannot be stopped when this is set to true.
+ *
+ * @return Whether all sessions are automatically recorded, or {@code null} if unknown.
+ */
+ @JsonProperty("auto_record")
+ public Boolean getAutoRecord() {
+ return autoRecord;
+ }
+
+ /**
+ * Record only the owner screen or any share screen of the video.
+ *
+ * @return Whether only the owner screen is recorded, or {@code null} if unknown.
+ */
+ @JsonProperty("record_only_owner")
+ public Boolean getRecordOnlyOwner() {
+ return recordOnlyOwner;
+ }
+
+ /**
+ * Entry point for constructing an instance of this class.
+ *
+ * @return A new Builder.
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static class Builder {
+ private Boolean autoRecord, recordOnlyOwner;
+
+ Builder() {}
+
+ /**
+ *
+ * @param autoRecord Automatically record all sessions in this room. Recording cannot be stopped when this is set to true.
+ *
+ * @return This builder.
+ */
+ public Builder autoRecord(boolean autoRecord) {
+ this.autoRecord = autoRecord;
+ return this;
+ }
+
+ /**
+ *
+ * @param recordOnlyOwner Record only the owner screen or any share screen of the video.
+ *
+ * @return This builder.
+ */
+ public Builder recordOnlyOwner(boolean recordOnlyOwner) {
+ this.recordOnlyOwner = recordOnlyOwner;
+ return this;
+ }
+
+
+ /**
+ * Builds the {@linkplain RecordingOptions}.
+ *
+ * @return An instance of RecordingOptions, populated with all fields from this builder.
+ */
+ public RecordingOptions build() {
+ return new RecordingOptions(this);
+ }
+ }
+}
diff --git a/src/main/java/com/vonage/client/meetings/RecordingStatus.java b/src/main/java/com/vonage/client/meetings/RecordingStatus.java
new file mode 100644
index 000000000..ab3df9f04
--- /dev/null
+++ b/src/main/java/com/vonage/client/meetings/RecordingStatus.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2023 Vonage
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.vonage.client.meetings;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonValue;
+
+/**
+ * Represents the recording state of a meeting.
+ */
+public enum RecordingStatus {
+ STARTED,
+ STOPPED,
+ PAUSED,
+ UPLOADED;
+
+ @JsonCreator
+ public static RecordingStatus fromString(String value) {
+ try {
+ return valueOf(value.toUpperCase());
+ }
+ catch (NullPointerException | IllegalArgumentException ex) {
+ return null;
+ }
+ }
+
+ @JsonValue
+ @Override
+ public String toString() {
+ return name().toLowerCase();
+ }
+}
diff --git a/src/main/java/com/vonage/client/meetings/RoomLanguage.java b/src/main/java/com/vonage/client/meetings/RoomLanguage.java
new file mode 100644
index 000000000..af03fe8a5
--- /dev/null
+++ b/src/main/java/com/vonage/client/meetings/RoomLanguage.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2023 Vonage
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.vonage.client.meetings;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonValue;
+
+/**
+ * Represents the language in {@link UISettings}. Default is English.
+ */
+public enum RoomLanguage {
+ /**
+ * English
+ */
+ EN,
+
+ /**
+ * Hebrew
+ */
+ HE,
+
+ /**
+ * Spanish
+ */
+ ES,
+
+ /**
+ * Portuguese
+ */
+ PT,
+
+ /**
+ * Italian
+ */
+ IT,
+
+ /**
+ * Catalan
+ */
+ CA,
+
+ /**
+ * French
+ */
+ FR,
+
+ /**
+ * German
+ */
+ DE;
+
+ @JsonCreator
+ public static RoomLanguage fromString(String value) {
+ try {
+ return valueOf(value.toUpperCase());
+ }
+ catch (NullPointerException | IllegalArgumentException ex) {
+ return null;
+ }
+ }
+
+ @JsonValue
+ @Override
+ public String toString() {
+ return name().toLowerCase();
+ }
+}
diff --git a/src/main/java/com/vonage/client/meetings/RoomLinks.java b/src/main/java/com/vonage/client/meetings/RoomLinks.java
new file mode 100644
index 000000000..6711f5074
--- /dev/null
+++ b/src/main/java/com/vonage/client/meetings/RoomLinks.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2023 Vonage
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.vonage.client.meetings;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.net.URI;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class RoomLinks {
+ @JsonProperty("host_url") UrlContainer hostUrl;
+ @JsonProperty("guest_url") UrlContainer guestUrl;
+
+ protected RoomLinks() {
+ }
+
+ /**
+ * @return The host URL.
+ */
+ public URI getHostUrl() {
+ return hostUrl != null ? hostUrl.getHref() : null;
+ }
+
+ /**
+ * @return The guest URL.
+ */
+ public URI getGuestUrl() {
+ return guestUrl != null ? guestUrl.getHref() : null;
+ }
+}
diff --git a/src/main/java/com/vonage/client/meetings/RoomType.java b/src/main/java/com/vonage/client/meetings/RoomType.java
new file mode 100644
index 000000000..ae727a6fd
--- /dev/null
+++ b/src/main/java/com/vonage/client/meetings/RoomType.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2023 Vonage
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.vonage.client.meetings;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonValue;
+
+/**
+ * Represents the type of meeting room.
+ */
+public enum RoomType {
+ /**
+ * "Instant" is active for 10 minutes until the first participant joins the room
+ * and remains active for 10 minutes after the last participant leaves.
+ */
+ INSTANT,
+
+ /**
+ * "Long term" expires after a specific date-time.
+ */
+ LONG_TERM;
+
+ @JsonCreator
+ public static RoomType fromString(String value) {
+ try {
+ return valueOf(value.toUpperCase());
+ }
+ catch (NullPointerException | IllegalArgumentException ex) {
+ return null;
+ }
+ }
+
+ @JsonValue
+ @Override
+ public String toString() {
+ return name().toLowerCase();
+ }
+}
diff --git a/src/main/java/com/vonage/client/meetings/SearchThemeRoomsEndpoint.java b/src/main/java/com/vonage/client/meetings/SearchThemeRoomsEndpoint.java
new file mode 100644
index 000000000..bd729b39e
--- /dev/null
+++ b/src/main/java/com/vonage/client/meetings/SearchThemeRoomsEndpoint.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2023 Vonage
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.vonage.client.meetings;
+
+import com.vonage.client.AbstractMethod;
+import com.vonage.client.HttpWrapper;
+import com.vonage.client.auth.JWTAuthMethod;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.RequestBuilder;
+import java.io.IOException;
+
+class SearchThemeRoomsEndpoint extends AbstractMethod {
+ private static final Class>[] ALLOWED_AUTH_METHODS = {JWTAuthMethod.class};
+ private static final String PATH = "/meetings/themes/%s/rooms";
+
+ SearchThemeRoomsEndpoint(HttpWrapper httpWrapper) {
+ super(httpWrapper);
+ }
+
+ @Override
+ protected Class>[] getAcceptableAuthMethods() {
+ return ALLOWED_AUTH_METHODS;
+ }
+
+ @Override
+ public RequestBuilder makeRequest(ListRoomsRequest request) {
+ String path = String.format(PATH, request.themeId);
+ String uri = httpWrapper.getHttpConfig().getApiEuBaseUri() + path;
+ return request.addParameters(RequestBuilder.get(uri))
+ .setHeader("Accept", "application/json");
+ }
+
+ @Override
+ public ListRoomsResponse parseResponse(HttpResponse response) throws IOException {
+ int statusCode = response.getStatusLine().getStatusCode();
+ if (statusCode >= 200 && statusCode < 300) {
+ return ListRoomsResponse.fromJson(basicResponseHandler.handleResponse(response));
+ }
+ else {
+ throw MeetingsResponseException.fromHttpResponse(response);
+ }
+ }
+}
diff --git a/src/main/java/com/vonage/client/meetings/Theme.java b/src/main/java/com/vonage/client/meetings/Theme.java
new file mode 100644
index 000000000..0d4a4bbab
--- /dev/null
+++ b/src/main/java/com/vonage/client/meetings/Theme.java
@@ -0,0 +1,336 @@
+/*
+ * Copyright 2023 Vonage
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.vonage.client.meetings;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.vonage.client.VonageResponseParseException;
+import com.vonage.client.VonageUnexpectedException;
+import java.io.IOException;
+import java.net.URI;
+import java.util.UUID;
+import java.util.regex.Pattern;
+
+@JsonInclude(value = JsonInclude.Include.NON_NULL)
+@JsonIgnoreProperties(ignoreUnknown = true, value = "theme_id", allowSetters = true)
+public class Theme {
+ static final Pattern COLOR_PATTERN = Pattern.compile("(#[a-fA-F0-9]{6}|[a-fA-F0-9]{3})");
+
+ UUID themeId;
+ private UUID applicationId;
+ private ThemeDomain domain;
+ private String themeName, mainColor, accountId, shortCompanyUrl,
+ brandText, brandImageColored, brandImageWhite, brandedFavicon;
+ private URI brandImageColoredUrl, brandImageWhiteUrl, brandedFaviconUrl;
+
+ protected Theme() {
+ }
+
+ Theme(Builder builder) {
+ if ((mainColor = builder.mainColor) != null && !COLOR_PATTERN.matcher(mainColor).matches()) {
+ throw new IllegalArgumentException("Main color must be a valid hex pallet.");
+ }
+
+ if ((brandText = builder.brandText) != null) {
+ if (brandText.length() > 200) {
+ throw new IllegalArgumentException("Brand text cannot exceed 200 characters.");
+ }
+ else if (brandText.trim().isEmpty()) {
+ throw new IllegalArgumentException("Brand text cannot be blank.");
+ }
+ }
+
+ if ((themeName = builder.themeName) != null) {
+ if (themeName.length() > 200) {
+ throw new IllegalArgumentException("Theme name cannot exceed 200 characters.");
+ }
+ else if (themeName.trim().isEmpty()) {
+ throw new IllegalArgumentException("Theme name cannot be blank.");
+ }
+ }
+
+ if ((shortCompanyUrl = builder.shortCompanyUrl) != null) {
+ if (shortCompanyUrl.length() > 128) {
+ throw new IllegalArgumentException("Short company URL cannot exceed 128 characters.");
+ }
+ else if (shortCompanyUrl.trim().isEmpty()) {
+ throw new IllegalArgumentException("Short company URL cannot be blank.");
+ }
+ }
+ }
+
+ /**
+ * Unique theme ID.
+ *
+ * @return The theme ID.
+ */
+ @JsonProperty("theme_id")
+ public UUID getThemeId() {
+ return themeId;
+ }
+
+ /**
+ * Theme name.
+ *
+ * @return Name of the theme.
+ */
+ @JsonProperty("theme_name")
+ public String getThemeName() {
+ return themeName;
+ }
+
+ /**
+ * The theme's application domain.
+ *
+ * @return The domain as an enum.
+ */
+ @JsonProperty("domain")
+ public ThemeDomain getDomain() {
+ return domain;
+ }
+
+ /**
+ * Account ID.
+ *
+ * @return The account ID (API key).
+ */
+ @JsonProperty("account_id")
+ public String getAccountId() {
+ return accountId;
+ }
+
+ /**
+ * Unique application ID.
+ *
+ * @return The application ID.
+ */
+ @JsonProperty("application_id")
+ public UUID getApplicationId() {
+ return applicationId;
+ }
+
+ /**
+ * Main colour hex code.
+ *
+ * @return The main theme colour (hex value).
+ */
+ @JsonProperty("main_color")
+ public String getMainColor() {
+ return mainColor;
+ }
+
+ /**
+ * Company name to include in the URL.
+ *
+ * @return The company's short URL.
+ */
+ @JsonProperty("short_company_url")
+ public String getShortCompanyUrl() {
+ return shortCompanyUrl;
+ }
+
+ /**
+ * Brand text that will appear in the application.
+ *
+ * @return The branding text.
+ */
+ @JsonProperty("brand_text")
+ public String getBrandText() {
+ return brandText;
+ }
+
+ /**
+ * Coloured logo's key in storage system.
+ *
+ * @return The brand image in colour.
+ */
+ @JsonProperty("brand_image_colored")
+ public String getBrandImageColored() {
+ return brandImageColored;
+ }
+
+ /**
+ * White logo's key in storage system.
+ *
+ * @return The brand image in white.
+ */
+ @JsonProperty("brand_image_white")
+ public String getBrandImageWhite() {
+ return brandImageWhite;
+ }
+
+ /**
+ * Favicon's key in storage system.
+ *
+ * @return The brand favicon key.
+ */
+ @JsonProperty("branded_favicon")
+ public String getBrandedFavicon() {
+ return brandedFavicon;
+ }
+
+ /**
+ * Coloured logo's link.
+ *
+ * @return The coloured brand image URL.
+ */
+ @JsonProperty("brand_image_colored_url")
+ public URI getBrandImageColoredUrl() {
+ return brandImageColoredUrl;
+ }
+
+ /**
+ * White logo's link.
+ *
+ * @return The white brand image URL.
+ */
+ @JsonProperty("brand_image_white_url")
+ public URI getBrandImageWhiteUrl() {
+ return brandImageWhiteUrl;
+ }
+
+ /**
+ * Favicon's link.
+ *
+ * @return The favicon URL.
+ */
+ @JsonProperty("branded_favicon_url")
+ public URI getBrandedFaviconUrl() {
+ return brandedFaviconUrl;
+ }
+
+ /**
+ * Updates (hydrates) this object's fields from additional JSON data.
+ *
+ * @param json The JSON payload.
+ */
+ public void updateFromJson(String json) {
+ try {
+ ObjectMapper mapper = new ObjectMapper();
+ mapper.readerForUpdating(this).readValue(json, getClass());
+ }
+ catch (IOException ex) {
+ throw new VonageResponseParseException("Failed to update "+getClass().getSimpleName()+" from json.", ex);
+ }
+ }
+
+ /**
+ * Creates an instance of this class from a JSON payload.
+ *
+ * @param json The JSON string to parse.
+ * @return An instance of this class with the fields populated, if present.
+ */
+ public static Theme fromJson(String json) {
+ try {
+ ObjectMapper mapper = new ObjectMapper();
+ return mapper.readValue(json, Theme.class);
+ }
+ catch (IOException ex) {
+ throw new VonageResponseParseException("Failed to produce Theme from json.", ex);
+ }
+ }
+
+ /**
+ * Generates a JSON payload from this request.
+ *
+ * @return JSON representation of this Theme object.
+ */
+ public String toJson() {
+ try {
+ ObjectMapper mapper = new ObjectMapper();
+ return mapper.writeValueAsString(this);
+ }
+ catch (JsonProcessingException jpe) {
+ throw new VonageUnexpectedException("Failed to produce JSON from "+getClass().getSimpleName()+" object.", jpe);
+ }
+ }
+
+ /**
+ * Entry point for constructing an instance of this class.
+ *
+ * @return A new Builder.
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static class Builder {
+ private String themeName, mainColor, brandText, shortCompanyUrl;
+
+ Builder() {}
+
+ /**
+ * (OPTIONAL) The name to give the theme (maximum 200 characters).
+ *
+ * @param themeName The theme name.
+ *
+ * @return This builder.
+ */
+ public Builder themeName(String themeName) {
+ this.themeName = themeName;
+ return this;
+ }
+
+ /**
+ * (REQUIRED) The main theme colour code (hex value).
+ *
+ * @param mainColor The colour's hex code, including the # character.
+ *
+ * @return This builder.
+ */
+ public Builder mainColor(String mainColor) {
+ this.mainColor = mainColor;
+ return this;
+ }
+
+ /**
+ * (OPTIONAL) The short company URL. Must be a valid URI, cannot exceed 128 characters.
+ *
+ * @param shortCompanyUrl The URL as a String.
+ *
+ * @return This builder.
+ */
+ public Builder shortCompanyUrl(String shortCompanyUrl) {
+ this.shortCompanyUrl = shortCompanyUrl;
+ return this;
+ }
+
+ /**
+ * (REQUIRED) The brand / logo text.
+ *
+ * @param brandText The text.
+ *
+ * @return This builder.
+ */
+ public Builder brandText(String brandText) {
+ this.brandText = brandText;
+ return this;
+ }
+
+
+ /**
+ * Builds the {@linkplain Theme}.
+ *
+ * @return An instance of Theme, populated with all fields from this builder.
+ */
+ public Theme build() {
+ return new Theme(this);
+ }
+ }
+}
diff --git a/src/main/java/com/vonage/client/meetings/ThemeDomain.java b/src/main/java/com/vonage/client/meetings/ThemeDomain.java
new file mode 100644
index 000000000..cdae3198c
--- /dev/null
+++ b/src/main/java/com/vonage/client/meetings/ThemeDomain.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2023 Vonage
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.vonage.client.meetings;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+
+/**
+ * Represents the application domain for a theme.
+ */
+public enum ThemeDomain {
+ VCP,
+ VBC;
+
+ @JsonCreator
+ public static ThemeDomain fromString(String value) {
+ try {
+ return valueOf(value.toUpperCase());
+ }
+ catch (NullPointerException | IllegalArgumentException ex) {
+ return null;
+ }
+ }
+}
diff --git a/src/main/java/com/vonage/client/meetings/UISettings.java b/src/main/java/com/vonage/client/meetings/UISettings.java
new file mode 100644
index 000000000..2c1efca17
--- /dev/null
+++ b/src/main/java/com/vonage/client/meetings/UISettings.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2023 Vonage
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.vonage.client.meetings;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+@JsonInclude(value = JsonInclude.Include.NON_NULL)
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class UISettings {
+ private RoomLanguage language;
+
+ UISettings(Builder builder) {
+ language = builder.language;
+ }
+
+ protected UISettings() {
+ }
+
+ /**
+ * Language setting.
+ *
+ * @return The language, as an enum.
+ */
+ @JsonProperty("language")
+ public RoomLanguage getLanguage() {
+ return language;
+ }
+
+ /**
+ * Entry point for constructing an instance of this class.
+ *
+ * @return A new Builder.
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static class Builder {
+ private RoomLanguage language;
+
+ Builder() {}
+
+ /**
+ *
+ * @param language The language setting.
+ *
+ * @return This builder.
+ */
+ public Builder language(RoomLanguage language) {
+ this.language = language;
+ return this;
+ }
+
+ /**
+ * Builds the {@linkplain UISettings}.
+ *
+ * @return An instance of UISettings, populated with all fields from this builder.
+ */
+ public UISettings build() {
+ return new UISettings(this);
+ }
+ }
+}
diff --git a/src/main/java/com/vonage/client/meetings/UpdateApplicationEndpoint.java b/src/main/java/com/vonage/client/meetings/UpdateApplicationEndpoint.java
new file mode 100644
index 000000000..72bc79cf1
--- /dev/null
+++ b/src/main/java/com/vonage/client/meetings/UpdateApplicationEndpoint.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2023 Vonage
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.vonage.client.meetings;
+
+import com.vonage.client.AbstractMethod;
+import com.vonage.client.HttpWrapper;
+import com.vonage.client.auth.JWTAuthMethod;
+import org.apache.http.HttpResponse;
+import org.apache.http.entity.ContentType;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.client.methods.RequestBuilder;
+import java.io.IOException;
+
+class UpdateApplicationEndpoint extends AbstractMethod {
+ private static final Class>[] ALLOWED_AUTH_METHODS = {JWTAuthMethod.class};
+ private static final String PATH = "/meetings/applications";
+
+ UpdateApplicationEndpoint(HttpWrapper httpWrapper) {
+ super(httpWrapper);
+ }
+
+ @Override
+ protected Class>[] getAcceptableAuthMethods() {
+ return ALLOWED_AUTH_METHODS;
+ }
+
+ @Override
+ public RequestBuilder makeRequest(UpdateApplicationRequest request) {
+ String uri = httpWrapper.getHttpConfig().getApiEuBaseUri() + PATH;
+ return RequestBuilder.patch(uri)
+ .setHeader("Content-Type", "application/json")
+ .setHeader("Accept", "application/json")
+ .setEntity(new StringEntity(request.toJson(), ContentType.APPLICATION_JSON));
+ }
+
+ @Override
+ public Application parseResponse(HttpResponse response) throws IOException {
+ int statusCode = response.getStatusLine().getStatusCode();
+ if (statusCode >= 200 && statusCode < 300) {
+ return Application.fromJson(basicResponseHandler.handleResponse(response));
+ }
+ else {
+ throw MeetingsResponseException.fromHttpResponse(response);
+ }
+ }
+}
diff --git a/src/main/java/com/vonage/client/meetings/UpdateApplicationRequest.java b/src/main/java/com/vonage/client/meetings/UpdateApplicationRequest.java
new file mode 100644
index 000000000..9ebb89511
--- /dev/null
+++ b/src/main/java/com/vonage/client/meetings/UpdateApplicationRequest.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2023 Vonage
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.vonage.client.meetings;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.vonage.client.VonageUnexpectedException;
+import java.util.Objects;
+import java.util.UUID;
+
+@JsonInclude(value = JsonInclude.Include.NON_NULL)
+public class UpdateApplicationRequest {
+ private final UUID defaultThemeId;
+
+ UpdateApplicationRequest(Builder builder) {
+ defaultThemeId = Objects.requireNonNull(builder.defaultThemeId, "Default theme ID is required.");
+ }
+
+ /**
+ * @return The theme ID to set as application default theme.
+ */
+ @JsonProperty("default_theme_id")
+ public UUID getDefaultThemeId() {
+ return defaultThemeId;
+ }
+
+ /**
+ * Generates a JSON payload from this request.
+ *
+ * @return JSON representation of this UpdateApplicationRequest object.
+ */
+ public String toJson() {
+ try {
+ ObjectMapper mapper = new ObjectMapper();
+ String nested = mapper.writeValueAsString(this);
+ return "{\"update_details\": "+nested+"}";
+ }
+ catch (JsonProcessingException jpe) {
+ throw new VonageUnexpectedException(
+ "Failed to produce JSON from "+getClass().getSimpleName()+" object.", jpe
+ );
+ }
+ }
+
+ /**
+ * Entry point for constructing an instance of this class.
+ *
+ * @return A new Builder.
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static class Builder {
+ private UUID defaultThemeId;
+
+ Builder() {}
+
+ /**
+ *
+ * @param defaultThemeId The theme ID to set as application default theme.
+ *
+ * @return This builder.
+ */
+ public Builder defaultThemeId(UUID defaultThemeId) {
+ this.defaultThemeId = defaultThemeId;
+ return this;
+ }
+
+
+ /**
+ * Builds the {@linkplain UpdateApplicationRequest}.
+ *
+ * @return An instance of UpdateApplicationRequest, populated with all fields from this builder.
+ */
+ public UpdateApplicationRequest build() {
+ return new UpdateApplicationRequest(this);
+ }
+ }
+}
diff --git a/src/main/java/com/vonage/client/meetings/UpdateRoomEndpoint.java b/src/main/java/com/vonage/client/meetings/UpdateRoomEndpoint.java
new file mode 100644
index 000000000..f64fb1252
--- /dev/null
+++ b/src/main/java/com/vonage/client/meetings/UpdateRoomEndpoint.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2023 Vonage
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.vonage.client.meetings;
+
+import com.vonage.client.AbstractMethod;
+import com.vonage.client.HttpWrapper;
+import com.vonage.client.auth.JWTAuthMethod;
+import org.apache.http.HttpResponse;
+import org.apache.http.entity.ContentType;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.client.methods.RequestBuilder;
+import java.io.IOException;
+
+class UpdateRoomEndpoint extends AbstractMethod {
+ private static final Class>[] ALLOWED_AUTH_METHODS = {JWTAuthMethod.class};
+ private static final String PATH = "/meetings/rooms/%s";
+
+ UpdateRoomEndpoint(HttpWrapper httpWrapper) {
+ super(httpWrapper);
+ }
+
+ @Override
+ protected Class>[] getAcceptableAuthMethods() {
+ return ALLOWED_AUTH_METHODS;
+ }
+
+ @Override
+ public RequestBuilder makeRequest(UpdateRoomRequest request) {
+ String path = String.format(PATH, request.roomId);
+ String uri = httpWrapper.getHttpConfig().getApiEuBaseUri() + path;
+ return RequestBuilder.patch(uri)
+ .setHeader("Content-Type", "application/json")
+ .setHeader("Accept", "application/json")
+ .setEntity(new StringEntity(request.toJson(), ContentType.APPLICATION_JSON));
+ }
+
+ @Override
+ public MeetingRoom parseResponse(HttpResponse response) throws IOException {
+ int statusCode = response.getStatusLine().getStatusCode();
+ if (statusCode >= 200 && statusCode < 300) {
+ return MeetingRoom.fromJson(basicResponseHandler.handleResponse(response));
+ }
+ else {
+ throw MeetingsResponseException.fromHttpResponse(response);
+ }
+ }
+}
diff --git a/src/main/java/com/vonage/client/meetings/UpdateRoomRequest.java b/src/main/java/com/vonage/client/meetings/UpdateRoomRequest.java
new file mode 100644
index 000000000..78aee8acf
--- /dev/null
+++ b/src/main/java/com/vonage/client/meetings/UpdateRoomRequest.java
@@ -0,0 +1,245 @@
+/*
+ * Copyright 2023 Vonage
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.vonage.client.meetings;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
+import com.vonage.client.VonageUnexpectedException;
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
+import java.util.UUID;
+
+@JsonInclude(value = JsonInclude.Include.NON_NULL)
+public class UpdateRoomRequest {
+ @JsonIgnore UUID roomId;
+
+ private final Boolean expireAfterUse;
+ private final InitialJoinOptions initialJoinOptions;
+ private final CallbackUrls callbackUrls;
+ private final AvailableFeatures availableFeatures;
+ private final JoinApprovalLevel joinApprovalLevel;
+ private final UUID themeId;
+ @JsonIgnore private final Instant expiresAt;
+
+ UpdateRoomRequest(Builder builder) {
+ expireAfterUse = builder.expireAfterUse;
+ initialJoinOptions = builder.initialJoinOptions;
+ callbackUrls = builder.callbackUrls;
+ availableFeatures = builder.availableFeatures;
+ joinApprovalLevel = builder.joinApprovalLevel;
+ themeId = builder.themeId;
+ MeetingRoom.validateExpiresAtAndRoomType(expiresAt = builder.expiresAt, null);
+ }
+
+ /**
+ * Close the room after a session ends. Only relevant for long_term rooms.
+ *
+ * @return {@code true} if the room will close after end of session, or {@code null} if unknown / not applicable.
+ */
+ @JsonProperty("expire_after_use")
+ public Boolean getExpireAfterUse() {
+ return expireAfterUse;
+ }
+
+ /**
+ * Options for when a participant joins a meeting.
+ *
+ * @return The initial joining options.
+ */
+ @JsonProperty("initial_join_options")
+ public InitialJoinOptions getInitialJoinOptions() {
+ return initialJoinOptions;
+ }
+
+ /**
+ * The callback URLs for this meeting.
+ *
+ * @return The CallbackUrls object.
+ */
+ @JsonProperty("callback_urls")
+ public CallbackUrls getCallbackUrls() {
+ return callbackUrls;
+ }
+
+ /**
+ * The available features for this meeting.
+ *
+ * @return The AvailableFeatures object.
+ */
+ @JsonProperty("available_features")
+ public AvailableFeatures getAvailableFeatures() {
+ return availableFeatures;
+ }
+
+ /**
+ * ID of the theme for this room.
+ *
+ * @return The theme ID.
+ */
+ @JsonProperty("theme_id")
+ public UUID getThemeId() {
+ return themeId;
+ }
+
+ /**
+ * The level of approval needed to join the meeting in the room.
+ *
+ * @return The approval level, as an enum.
+ */
+ @JsonProperty("join_approval_level")
+ public JoinApprovalLevel getJoinApprovalLevel() {
+ return joinApprovalLevel;
+ }
+
+ /**
+ * The time for when the room will be expired, expressed in ISO 8601 format.
+ *
+ * @return The room expiration time.
+ */
+ @JsonProperty("expires_at")
+ public Instant getExpiresAt() {
+ return expiresAt;
+ }
+
+ /**
+ * Generates a JSON payload from this request.
+ *
+ * @return JSON representation of this UpdateRoomRequest object.
+ */
+ public String toJson() {
+ try {
+ ObjectMapper mapper = new ObjectMapper();
+ mapper.registerModule(new JavaTimeModule());
+ mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
+ return "{\"update_details\":"+mapper.writeValueAsString(this)+"}";
+ }
+ catch (JsonProcessingException jpe) {
+ throw new VonageUnexpectedException("Failed to produce JSON from "+getClass().getSimpleName()+" object.", jpe);
+ }
+ }
+
+ /**
+ * Entry point for constructing an instance of this class.
+ *
+ * @return A new Builder.
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static class Builder {
+ private Boolean expireAfterUse;
+ private InitialJoinOptions initialJoinOptions;
+ private CallbackUrls callbackUrls;
+ private AvailableFeatures availableFeatures;
+ private JoinApprovalLevel joinApprovalLevel;
+ private UUID themeId;
+ private Instant expiresAt;
+
+ Builder() {}
+
+ /**
+ *
+ * @param expireAfterUse Close the room after a session ends. Only relevant for long_term rooms.
+ *
+ * @return This builder.
+ */
+ public Builder expireAfterUse(Boolean expireAfterUse) {
+ this.expireAfterUse = expireAfterUse;
+ return this;
+ }
+
+ /**
+ *
+ * @param initialJoinOptions Options for when a participant joins a meeting.
+ *
+ * @return This builder.
+ */
+ public Builder initialJoinOptions(InitialJoinOptions initialJoinOptions) {
+ this.initialJoinOptions = initialJoinOptions;
+ return this;
+ }
+
+ /**
+ *
+ * @param callbackUrls The callback URLs for this meeting.
+ *
+ * @return This builder.
+ */
+ public Builder callbackUrls(CallbackUrls callbackUrls) {
+ this.callbackUrls = callbackUrls;
+ return this;
+ }
+
+ /**
+ *
+ * @param availableFeatures The available features for this meeting.
+ *
+ * @return This builder.
+ */
+ public Builder availableFeatures(AvailableFeatures availableFeatures) {
+ this.availableFeatures = availableFeatures;
+ return this;
+ }
+
+ /**
+ *
+ * @param themeId ID of the theme for this room.
+ *
+ * @return This builder.
+ */
+ public Builder themeId(UUID themeId) {
+ this.themeId = themeId;
+ return this;
+ }
+
+ /**
+ *
+ * @param joinApprovalLevel The level of approval needed to join the meeting in the room.
+ *
+ * @return This builder.
+ */
+ public Builder joinApprovalLevel(JoinApprovalLevel joinApprovalLevel) {
+ this.joinApprovalLevel = joinApprovalLevel;
+ return this;
+ }
+
+ /**
+ *
+ * @param expiresAt The time for when the room will be expired, expressed in ISO 8601 format.
+ *
+ * @return This builder.
+ */
+ public Builder expiresAt(Instant expiresAt) {
+ this.expiresAt = expiresAt.truncatedTo(ChronoUnit.MILLIS);
+ return this;
+ }
+
+ /**
+ * Builds the {@linkplain UpdateRoomRequest}.
+ *
+ * @return An instance of UpdateRoomRequest, populated with all fields from this builder.
+ */
+ public UpdateRoomRequest build() {
+ return new UpdateRoomRequest(this);
+ }
+ }
+}
diff --git a/src/main/java/com/vonage/client/meetings/UpdateThemeEndpoint.java b/src/main/java/com/vonage/client/meetings/UpdateThemeEndpoint.java
new file mode 100644
index 000000000..207e37a09
--- /dev/null
+++ b/src/main/java/com/vonage/client/meetings/UpdateThemeEndpoint.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2023 Vonage
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.vonage.client.meetings;
+
+import com.vonage.client.AbstractMethod;
+import com.vonage.client.HttpWrapper;
+import com.vonage.client.auth.JWTAuthMethod;
+import org.apache.http.HttpResponse;
+import org.apache.http.entity.ContentType;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.client.methods.RequestBuilder;
+import java.io.IOException;
+
+class UpdateThemeEndpoint extends AbstractMethod {
+ private static final Class>[] ALLOWED_AUTH_METHODS = {JWTAuthMethod.class};
+ private static final String PATH = "/meetings/themes/%s";
+ private Theme cachedTheme;
+
+ UpdateThemeEndpoint(HttpWrapper httpWrapper) {
+ super(httpWrapper);
+ }
+
+ @Override
+ protected Class>[] getAcceptableAuthMethods() {
+ return ALLOWED_AUTH_METHODS;
+ }
+
+ @Override
+ public RequestBuilder makeRequest(Theme request) {
+ String path = String.format(PATH, request.getThemeId());
+ String uri = httpWrapper.getHttpConfig().getApiEuBaseUri() + path;
+ String json = "{\"update_details\":" + (cachedTheme = request).toJson() + "}";
+ return RequestBuilder.patch(uri)
+ .setHeader("Content-Type", "application/json")
+ .setHeader("Accept", "application/json")
+ .setEntity(new StringEntity(json, ContentType.APPLICATION_JSON));
+ }
+
+ @Override
+ public Theme parseResponse(HttpResponse response) throws IOException {
+ try {
+ int statusCode = response.getStatusLine().getStatusCode();
+ if (statusCode >= 200 && statusCode < 300) {
+ String json = basicResponseHandler.handleResponse(response);
+ if (cachedTheme != null) {
+ cachedTheme.updateFromJson(json);
+ return cachedTheme;
+ }
+ else {
+ return Theme.fromJson(json);
+ }
+ }
+ else {
+ throw MeetingsResponseException.fromHttpResponse(response);
+ }
+ }
+ finally {
+ cachedTheme = null;
+ }
+ }
+}
diff --git a/src/main/java/com/vonage/client/meetings/UrlContainer.java b/src/main/java/com/vonage/client/meetings/UrlContainer.java
new file mode 100644
index 000000000..7160033e5
--- /dev/null
+++ b/src/main/java/com/vonage/client/meetings/UrlContainer.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2023 Vonage
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.vonage.client.meetings;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.net.URI;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class UrlContainer {
+ URI href;
+
+ protected UrlContainer() {
+ }
+
+ /**
+ * @return The URL.
+ */
+ @JsonProperty("href")
+ public URI getHref() {
+ return href;
+ }
+}
diff --git a/src/main/java/com/vonage/client/meetings/package-info.java b/src/main/java/com/vonage/client/meetings/package-info.java
new file mode 100644
index 000000000..e4c4b33be
--- /dev/null
+++ b/src/main/java/com/vonage/client/meetings/package-info.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2023 Vonage
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * This package contains classes and sub-packages to support usage of the
+ * Meetings API.
+ *
+ * Please refer to
+ * the developer documentation for an overview of the concepts.
+ *
+ * @since 7.6.0
+ */
+package com.vonage.client.meetings;
\ No newline at end of file
diff --git a/src/main/java/com/vonage/client/proactiveconnect/ProactiveConnectClient.java b/src/main/java/com/vonage/client/proactiveconnect/ProactiveConnectClient.java
index 072b29d24..e86681971 100644
--- a/src/main/java/com/vonage/client/proactiveconnect/ProactiveConnectClient.java
+++ b/src/main/java/com/vonage/client/proactiveconnect/ProactiveConnectClient.java
@@ -296,7 +296,7 @@ public void deleteListItem(UUID listId, UUID itemId) {
*
* @param listId Unique ID of the list.
*
- * @return The list items CSV file contents as a String.
+ * @return The list items contents as a CSV-formatted String.
* @see #downloadListItems(UUID, Path)
*
* @throws ProactiveConnectResponseException If the list does not exist or couldn't be retrieved.
@@ -309,7 +309,7 @@ public String downloadListItems(UUID listId) {
/**
* Download all items in a list in CSV format.
- * Use {@link #downloadListItems(UUID)} to get the results as a raw binary.
+ * Use {@link #downloadListItems(UUID)} to get the results as a String.
*
* @param listId Unique ID of the list.
* @param file Path of the file to write the downloaded results to.
diff --git a/src/main/java/com/vonage/client/proactiveconnect/ProactiveConnectResponseException.java b/src/main/java/com/vonage/client/proactiveconnect/ProactiveConnectResponseException.java
index 469b921fc..22b9cd01e 100644
--- a/src/main/java/com/vonage/client/proactiveconnect/ProactiveConnectResponseException.java
+++ b/src/main/java/com/vonage/client/proactiveconnect/ProactiveConnectResponseException.java
@@ -18,11 +18,9 @@
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
-import com.fasterxml.jackson.annotation.JsonProperty;
import com.vonage.client.VonageApiResponseException;
import org.apache.http.HttpResponse;
import java.io.IOException;
-import java.util.List;
/**
* Response returned when an error is encountered (i.e. the API returns a non-2xx status code).
@@ -30,23 +28,11 @@
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
public final class ProactiveConnectResponseException extends VonageApiResponseException {
- List> errors;
void setStatusCode(int statusCode) {
this.statusCode = statusCode;
}
- /**
- * Additional description of problems encountered with the request.
- * This is typically only applicable to 400 or 409 error codes.
- *
- * @return The list of errors returned from the server (could be a Map or String), or {@code null} if none.
- */
- @JsonProperty("errors")
- public List> getErrors() {
- return errors;
- }
-
/**
* Creates an instance of this class from a JSON payload.
*
diff --git a/src/main/java/com/vonage/client/proactiveconnect/package-info.java b/src/main/java/com/vonage/client/proactiveconnect/package-info.java
index 671cad0a9..da4466564 100644
--- a/src/main/java/com/vonage/client/proactiveconnect/package-info.java
+++ b/src/main/java/com/vonage/client/proactiveconnect/package-info.java
@@ -17,7 +17,7 @@
/**
* This package contains classes and sub-packages to support usage of the
* Vonage Proactive Connect API.
- *
+ *
*
* @since 7.6.0
*/
diff --git a/src/main/java/com/vonage/client/verify2/VerificationCallback.java b/src/main/java/com/vonage/client/verify2/VerificationCallback.java
index 2accbcfa4..a9adff0ba 100644
--- a/src/main/java/com/vonage/client/verify2/VerificationCallback.java
+++ b/src/main/java/com/vonage/client/verify2/VerificationCallback.java
@@ -21,7 +21,6 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.vonage.client.VonageResponseParseException;
-import com.vonage.client.VonageUnexpectedException;
import java.io.IOException;
import java.net.URI;
import java.time.Instant;
@@ -179,7 +178,7 @@ public URI getSilentAuthUrl() {
* @param json The webhook response JSON string.
*
* @return The deserialized webhook response object.
- * @throws VonageUnexpectedException If the response could not be deserialized.
+ * @throws VonageResponseParseException If the response could not be deserialized.
*/
public static VerificationCallback fromJson(String json) {
try {
diff --git a/src/test/java/com/vonage/client/BugRepro.java b/src/test/java/com/vonage/client/BugRepro.java
index 15bd56ab1..1d0c36512 100644
--- a/src/test/java/com/vonage/client/BugRepro.java
+++ b/src/test/java/com/vonage/client/BugRepro.java
@@ -25,22 +25,21 @@
* Convenience class for debugging / live testing.
*/
public class BugRepro {
- static final String
- VONAGE_API_KEY = System.getenv("VONAGE_API_KEY"),
- VONAGE_API_SECRET = System.getenv("VONAGE_API_SECRET"),
- VONAGE_APPLICATION_ID = System.getenv("VONAGE_APPLICATION_ID"),
- VONAGE_PRIVATE_KEY_PATH = System.getenv("VONAGE_PRIVATE_KEY_PATH");
-
public static void main(String[] args) throws Throwable {
VonageClient client = VonageClient.builder()
- .apiKey(VONAGE_API_KEY)
- .apiSecret(VONAGE_API_SECRET)
- .privateKeyPath(VONAGE_PRIVATE_KEY_PATH)
- .applicationId(VONAGE_APPLICATION_ID)
+ .apiKey(System.getenv("VONAGE_API_KEY"))
+ .apiSecret(System.getenv("VONAGE_API_SECRET"))
+ .applicationId(System.getenv("VONAGE_APPLICATION_ID"))
+ .privateKeyPath(System.getenv("VONAGE_PRIVATE_KEY_PATH"))
.build();
- // Debug code here
+ try {
+ // Debug code here
- System.out.println("Success");
+ System.out.println("Success");
+ }
+ catch (Exception ex) {
+ ex.printStackTrace();
+ }
}
}
diff --git a/src/test/java/com/vonage/client/ClientTest.java b/src/test/java/com/vonage/client/ClientTest.java
index 801fa2e0d..2c51e1d63 100644
--- a/src/test/java/com/vonage/client/ClientTest.java
+++ b/src/test/java/com/vonage/client/ClientTest.java
@@ -23,12 +23,15 @@
import org.apache.http.StatusLine;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpUriRequest;
-import static org.mockito.Mockito.*;
import static org.junit.Assert.assertThrows;
import org.junit.function.ThrowingRunnable;
+import static org.mockito.Mockito.*;
import java.io.ByteArrayInputStream;
+import java.io.InputStream;
import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
import java.util.UUID;
+import java.util.function.Function;
import java.util.function.Supplier;
public abstract class ClientTest {
@@ -48,7 +51,7 @@ protected HttpClient stubHttpClient(int statusCode) throws Exception {
return stubHttpClient(statusCode, "");
}
- protected HttpClient stubHttpClient(int statusCode, String content) throws Exception {
+ protected HttpClient stubHttpClient(int statusCode, String content, String... additionalReturns) throws Exception {
HttpClient result = mock(HttpClient.class);
HttpResponse response = mock(HttpResponse.class);
@@ -57,7 +60,9 @@ protected HttpClient stubHttpClient(int statusCode, String content) throws Excep
when(result.execute(any(HttpUriRequest.class))).thenReturn(response);
when(LoggingUtils.logResponse(any(HttpResponse.class))).thenReturn("response logged");
- when(entity.getContent()).thenReturn(new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8)));
+ Function transformation = c -> new ByteArrayInputStream(c.getBytes(StandardCharsets.UTF_8));
+ InputStream[] contentsEncoded = Arrays.stream(additionalReturns).map(transformation).toArray(InputStream[]::new);
+ when(entity.getContent()).thenReturn(transformation.apply(content), contentsEncoded);
when(sl.getStatusCode()).thenReturn(statusCode);
when(sl.getReasonPhrase()).thenReturn("Test reason");
when(response.getStatusLine()).thenReturn(sl);
diff --git a/src/test/java/com/vonage/client/VonageApiResponseExceptionTest.java b/src/test/java/com/vonage/client/VonageApiResponseExceptionTest.java
index 2f00bff45..22650c7e9 100644
--- a/src/test/java/com/vonage/client/VonageApiResponseExceptionTest.java
+++ b/src/test/java/com/vonage/client/VonageApiResponseExceptionTest.java
@@ -21,6 +21,8 @@
import static org.junit.Assert.*;
import org.junit.Test;
import java.net.URI;
+import java.util.Collections;
+import java.util.List;
public class VonageApiResponseExceptionTest {
@@ -61,13 +63,19 @@ public void testConstructFromValidJson() {
" \"type\": \""+type+"\",\n" +
" \"title\": \""+title+"\",\n" +
" \"detail\": \""+detail+"\",\n" +
- " \"instance\": \""+instance+"\"\n" +
+ " \"instance\": \""+instance+"\",\n" +
+ " \"errors\": [\"Bad name\", {\"errorKey\": \"errorValue\"}]\n" +
"}"
);
actual.statusCode = status;
assertEquals(expected, actual);
assertEquals(expected.hashCode(), actual.hashCode());
assertEquals(expected.toString(), actual.toString());
+ List> errors = actual.getErrors();
+ assertNotNull(errors);
+ assertEquals(2, errors.size());
+ assertEquals("Bad name", errors.get(0));
+ assertEquals(Collections.singletonMap("errorKey", "errorValue"), errors.get(1));
}
@Test
@@ -113,4 +121,27 @@ public void testToJsonIncludesFields() {
assertTrue(crx.dummy);
assertEquals(json, crx.toJson());
}
+
+ @Test(expected = VonageUnexpectedException.class)
+ public void triggerJsonProcessingException() throws Exception {
+ class SelfReferencing extends VonageApiResponseException {
+ @JsonProperty("self") final SelfReferencing self = this;
+ }
+ SelfReferencing selfReferencing = new SelfReferencing();
+ selfReferencing.toJson();
+ try {
+ throw selfReferencing;
+ }
+ catch (Exception ex) {
+ fail();
+ }
+ }
+
+ @Test(expected = VonageUnexpectedException.class)
+ public void testFromJsonWithBadConstructorDefinition() throws Exception {
+ class MissingNoArgs extends VonageApiResponseException {
+ public MissingNoArgs(boolean dummy) {}
+ }
+ VonageApiResponseException.fromJson(MissingNoArgs.class, "");
+ }
}
diff --git a/src/test/java/com/vonage/client/VonageClientTest.java b/src/test/java/com/vonage/client/VonageClientTest.java
index 6c9d6ca8f..625c64f42 100644
--- a/src/test/java/com/vonage/client/VonageClientTest.java
+++ b/src/test/java/com/vonage/client/VonageClientTest.java
@@ -333,6 +333,7 @@ public void testSubClientGetters() {
assertNotNull(client.getConversionClient());
assertNotNull(client.getVoiceClient());
assertNotNull(client.getInsightClient());
+ assertNotNull(client.getMeetingsClient());
assertNotNull(client.getMessagesClient());
assertNotNull(client.getNumbersClient());
assertNotNull(client.getProactiveConnectClient());
diff --git a/src/test/java/com/vonage/client/meetings/CreateRoomEndpointTest.java b/src/test/java/com/vonage/client/meetings/CreateRoomEndpointTest.java
new file mode 100644
index 000000000..ae6698ce2
--- /dev/null
+++ b/src/test/java/com/vonage/client/meetings/CreateRoomEndpointTest.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2023 Vonage
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.vonage.client.meetings;
+
+import com.vonage.client.HttpConfig;
+import com.vonage.client.HttpWrapper;
+import com.vonage.client.TestUtils;
+import com.vonage.client.auth.JWTAuthMethod;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.RequestBuilder;
+import org.apache.http.entity.ContentType;
+import org.apache.http.util.EntityUtils;
+import static org.junit.Assert.*;
+import org.junit.Before;
+import org.junit.Test;
+import java.time.Instant;
+import java.util.UUID;
+
+public class CreateRoomEndpointTest {
+ private CreateRoomEndpoint endpoint;
+
+ @Before
+ public void setUp() {
+ endpoint = new CreateRoomEndpoint(new HttpWrapper(new JWTAuthMethod("app-id", new byte[0])));
+ }
+
+ @Test
+ public void testDefaultUriAllParameters() throws Exception {
+ MeetingRoom request = MeetingRoom.builder("Srs bzns meeting")
+ .metadata("code=1123")
+ .type(RoomType.LONG_TERM)
+ .isAvailable(false)
+ .expireAfterUse(true)
+ .recordingOptions(RecordingOptions.builder().autoRecord(true).recordOnlyOwner(false).build())
+ .initialJoinOptions(InitialJoinOptions.builder().microphoneState(MicrophoneState.OFF).build())
+ .callbackUrls(CallbackUrls.builder()
+ .recordingsCallbackUrl("example.com/re")
+ .roomsCallbackUrl("example.com/ro")
+ .sessionsCallbackUrl("example.com/se")
+ .build()
+ )
+ .availableFeatures(AvailableFeatures.builder()
+ .isChatAvailable(false)
+ .isLocaleSwitcherAvailable(false)
+ .isRecordingAvailable(true)
+ .isWhiteboardAvailable(false)
+ .build()
+ )
+ .themeId(UUID.fromString("ef2b46f3-8ebb-437e-a671-272e4990fbc8"))
+ .joinApprovalLevel(JoinApprovalLevel.EXPLICIT_APPROVAL)
+ .expiresAt(Instant.MAX)
+ .uiSettings(UISettings.builder().language(RoomLanguage.IT).build())
+ .build();
+
+ RequestBuilder builder = endpoint.makeRequest(request);
+ assertEquals("POST", builder.getMethod());
+ String expectedUri = "https://api-eu.vonage.com/meetings/rooms";
+ assertEquals(expectedUri, builder.build().getURI().toString());
+ assertEquals(ContentType.APPLICATION_JSON.getMimeType(), builder.getFirstHeader("Content-Type").getValue());
+
+ String expectedRequest = "{\"expires_at\":\"+1000000000-12-31T23:59:59.999Z\"," +
+ "\"theme_id\":\"ef2b46f3-8ebb-437e-a671-272e4990fbc8\"," +
+ "\"display_name\":\"Srs bzns meeting\",\"metadata\":\"code=1123\"," +
+ "\"is_available\":false,\"expire_after_use\":true,\"type\":\"long_term\"," +
+ "\"join_approval_level\":\"explicit_approval\",\"recording_options\":" +
+ "{\"auto_record\":true,\"record_only_owner\":false}," +
+ "\"initial_join_options\":{\"microphone_state\":\"off\"}," +
+ "\"ui_settings\":{\"language\":\"it\"},\"callback_urls\":" +
+ "{\"rooms_callback_url\":\"example.com/ro\"," +
+ "\"sessions_callback_url\":\"example.com/se\"," +
+ "\"recordings_callback_url\":\"example.com/re\"}," +
+ "\"available_features\":{" +
+ "\"is_recording_available\":true,\"is_chat_available\":false," +
+ "\"is_whiteboard_available\":false,\"is_locale_switcher_available\":false}}";
+
+ assertEquals(expectedRequest, EntityUtils.toString(builder.getEntity()));
+ assertEquals(ContentType.APPLICATION_JSON.getMimeType(), builder.getFirstHeader("Accept").getValue());
+ String expectedResponse = "{\"meeting_code\":\"1234567890\",\"nonsense\":true,\"available_features\":{}}";
+ HttpResponse mockResponse = TestUtils.makeJsonHttpResponse(200, expectedResponse);
+ MeetingRoom parsed = endpoint.parseResponse(mockResponse);
+ assertEquals(request, parsed);
+
+ assertEquals("1234567890", parsed.getMeetingCode());
+ AvailableFeatures availableFeatures = parsed.getAvailableFeatures();
+ assertNotNull(availableFeatures);
+ assertNull(availableFeatures.getIsChatAvailable());
+ assertNull(availableFeatures.getIsRecordingAvailable());
+ assertNull(availableFeatures.getIsWhiteboardAvailable());
+ assertNull(availableFeatures.getIsLocaleSwitcherAvailable());
+ }
+
+ @Test
+ public void testCustomUri() throws Exception {
+ String baseUri = "http://example.com";
+ HttpWrapper wrapper = new HttpWrapper(HttpConfig.builder().baseUri(baseUri).build());
+ endpoint = new CreateRoomEndpoint(wrapper);
+ String expectedUri = baseUri + "/meetings/rooms";
+ String displayName = "My custom room";
+ MeetingRoom request = MeetingRoom.builder(displayName).build();
+ RequestBuilder builder = endpoint.makeRequest(request);
+ assertEquals(expectedUri, builder.build().getURI().toString());
+ assertEquals(ContentType.APPLICATION_JSON.getMimeType(), builder.getFirstHeader("Content-Type").getValue());
+ String expectedRequest = "{\"display_name\":\""+displayName+"\"}";
+ assertEquals(expectedRequest, EntityUtils.toString(builder.getEntity()));
+ assertEquals(ContentType.APPLICATION_JSON.getMimeType(), builder.getFirstHeader("Accept").getValue());
+ assertEquals("POST", builder.getMethod());
+ }
+
+ @Test
+ public void testParseResponseWithoutRequest() throws Exception {
+ MeetingRoom parsed = endpoint.parseResponse(TestUtils.makeJsonHttpResponse(201, "{}"));
+ assertNotNull(parsed);
+ }
+
+ @Test(expected = MeetingsResponseException.class)
+ public void testUnsuccessfulResponse() throws Exception {
+ endpoint.parseResponse(TestUtils.makeJsonHttpResponse(400, "{}"));
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/vonage/client/meetings/CreateThemeEndpointTest.java b/src/test/java/com/vonage/client/meetings/CreateThemeEndpointTest.java
new file mode 100644
index 000000000..dff9354be
--- /dev/null
+++ b/src/test/java/com/vonage/client/meetings/CreateThemeEndpointTest.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2023 Vonage
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.vonage.client.meetings;
+
+import com.vonage.client.HttpConfig;
+import com.vonage.client.HttpWrapper;
+import com.vonage.client.TestUtils;
+import com.vonage.client.auth.JWTAuthMethod;
+import org.apache.http.client.methods.RequestBuilder;
+import org.apache.http.entity.ContentType;
+import org.apache.http.util.EntityUtils;
+import static org.junit.Assert.*;
+import org.junit.Before;
+import org.junit.Test;
+
+public class CreateThemeEndpointTest {
+ private CreateThemeEndpoint endpoint;
+
+ @Before
+ public void setUp() {
+ endpoint = new CreateThemeEndpoint(new HttpWrapper(new JWTAuthMethod("app-id", new byte[0])));
+ }
+
+ @Test
+ public void testMakeRequestAllParameters() throws Exception {
+ Theme request = Theme.builder()
+ .themeName("Test theme").shortCompanyUrl("https://developer.vonage.com")
+ .mainColor("#AB34C1").brandText("Vonage").build();
+
+ RequestBuilder builder = endpoint.makeRequest(request);
+ assertEquals("POST", builder.getMethod());
+ String expectedUri = "https://api-eu.vonage.com/meetings/themes";
+ assertEquals(expectedUri, builder.build().getURI().toString());
+ assertEquals(ContentType.APPLICATION_JSON.getMimeType(), builder.getFirstHeader("Content-Type").getValue());
+ assertEquals(ContentType.APPLICATION_JSON.getMimeType(), builder.getFirstHeader("Accept").getValue());
+
+ String expectedRequest = "{\"theme_name\":\"Test theme\"," +
+ "\"main_color\":\"#AB34C1\"," +
+ "\"short_company_url\":\"https://developer.vonage.com\"," +
+ "\"brand_text\":\"Vonage\"}";
+
+ assertEquals(expectedRequest, EntityUtils.toString(builder.getEntity()));
+
+ String expectedResponse = "{\"theme_name\":\"fancy\",\"nonsense\":false,\"branded_favicon\":\"AWS-fav\"}";
+ Theme parsed = endpoint.parseResponse(TestUtils.makeJsonHttpResponse(201, expectedResponse));
+ assertEquals(request, parsed);
+ assertEquals("fancy", parsed.getThemeName());
+ assertEquals("AWS-fav", parsed.getBrandedFavicon());
+ }
+
+ @Test
+ public void testCustomUri() throws Exception {
+ String baseUri = "http://example.com";
+ HttpWrapper wrapper = new HttpWrapper(HttpConfig.builder().baseUri(baseUri).build());
+ endpoint = new CreateThemeEndpoint(wrapper);
+ String expectedUri = baseUri + "/meetings/themes";
+ Theme request = Theme.builder().build();
+ RequestBuilder builder = endpoint.makeRequest(request);
+ assertEquals(expectedUri, builder.build().getURI().toString());
+ assertEquals(ContentType.APPLICATION_JSON.getMimeType(), builder.getFirstHeader("Content-Type").getValue());
+ String expectedRequest = "{}";
+ assertEquals(expectedRequest, EntityUtils.toString(builder.getEntity()));
+ assertEquals(ContentType.APPLICATION_JSON.getMimeType(), builder.getFirstHeader("Accept").getValue());
+ assertEquals("POST", builder.getMethod());
+ }
+
+ @Test
+ public void testParseResponseWithoutRequest() throws Exception {
+ Theme parsed = endpoint.parseResponse(TestUtils.makeJsonHttpResponse(201, "{}"));
+ assertNotNull(parsed);
+ }
+
+ @Test(expected = MeetingsResponseException.class)
+ public void testUnsuccessfulResponse() throws Exception {
+ endpoint.parseResponse(TestUtils.makeJsonHttpResponse(400, "{}"));
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/vonage/client/meetings/DeleteRecordingEndpointTest.java b/src/test/java/com/vonage/client/meetings/DeleteRecordingEndpointTest.java
new file mode 100644
index 000000000..067b8ac8c
--- /dev/null
+++ b/src/test/java/com/vonage/client/meetings/DeleteRecordingEndpointTest.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2023 Vonage
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.vonage.client.meetings;
+
+import com.vonage.client.HttpConfig;
+import com.vonage.client.HttpWrapper;
+import com.vonage.client.TestUtils;
+import com.vonage.client.auth.JWTAuthMethod;
+import org.apache.http.client.methods.RequestBuilder;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import org.junit.Before;
+import org.junit.Test;
+import java.util.UUID;
+
+public class DeleteRecordingEndpointTest {
+ private DeleteRecordingEndpoint endpoint;
+
+ @Before
+ public void setUp() {
+ endpoint = new DeleteRecordingEndpoint(new HttpWrapper(new JWTAuthMethod("app-id", new byte[0])));
+ }
+
+ @Test
+ public void testDefaultUri() throws Exception {
+ UUID recordingId = UUID.randomUUID();
+ RequestBuilder builder = endpoint.makeRequest(recordingId);
+ assertEquals("DELETE", builder.getMethod());
+ String expectedUri = "https://api-eu.vonage.com/meetings/recordings/"+recordingId;
+ assertEquals(expectedUri, builder.build().getURI().toString());
+ assertNull(endpoint.parseResponse(TestUtils.makeJsonHttpResponse(204, "")));
+ }
+
+ @Test
+ public void testCustomUri() throws Exception {
+ String baseUri = "http://example.com";
+ UUID recordingId = UUID.randomUUID();
+ HttpWrapper wrapper = new HttpWrapper(HttpConfig.builder().baseUri(baseUri).build());
+ endpoint = new DeleteRecordingEndpoint(wrapper);
+ String expectedUri = baseUri + "/meetings/recordings/"+recordingId;
+ RequestBuilder builder = endpoint.makeRequest(recordingId);
+ assertEquals(expectedUri, builder.build().getURI().toString());
+ assertEquals("DELETE", builder.getMethod());
+ }
+
+ @Test(expected = MeetingsResponseException.class)
+ public void testUnsuccessfulResponse() throws Exception {
+ endpoint.parseResponse(TestUtils.makeJsonHttpResponse(400, "{}"));
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/vonage/client/meetings/DeleteThemeEndpointTest.java b/src/test/java/com/vonage/client/meetings/DeleteThemeEndpointTest.java
new file mode 100644
index 000000000..1eaf34c99
--- /dev/null
+++ b/src/test/java/com/vonage/client/meetings/DeleteThemeEndpointTest.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2023 Vonage
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.vonage.client.meetings;
+
+import com.vonage.client.HttpConfig;
+import com.vonage.client.HttpWrapper;
+import com.vonage.client.TestUtils;
+import com.vonage.client.auth.JWTAuthMethod;
+import org.apache.http.client.methods.RequestBuilder;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import org.junit.Before;
+import org.junit.Test;
+import java.util.UUID;
+
+public class DeleteThemeEndpointTest {
+ private DeleteThemeEndpoint endpoint;
+
+ @Before
+ public void setUp() {
+ endpoint = new DeleteThemeEndpoint(new HttpWrapper(new JWTAuthMethod("app-id", new byte[0])));
+ }
+
+ @Test
+ public void testDefaultUri() throws Exception {
+ UUID themeId = UUID.randomUUID();
+ RequestBuilder builder = endpoint.makeRequest(new DeleteThemeRequest(themeId, false));
+ assertEquals("DELETE", builder.getMethod());
+ String expectedUri = "https://api-eu.vonage.com/meetings/themes/"+themeId+"?force=false";
+ assertEquals(expectedUri, builder.build().getURI().toString());
+ assertNull(endpoint.parseResponse(TestUtils.makeJsonHttpResponse(204, "")));
+ }
+
+ @Test
+ public void testCustomUri() throws Exception {
+ UUID themeId = UUID.randomUUID();
+ String baseUri = "http://example.com";
+ HttpWrapper wrapper = new HttpWrapper(HttpConfig.builder().baseUri(baseUri).build());
+ endpoint = new DeleteThemeEndpoint(wrapper);
+ String expectedUri = baseUri + "/meetings/themes/"+themeId+"?force=true";
+ RequestBuilder builder = endpoint.makeRequest(new DeleteThemeRequest(themeId, true));
+ assertEquals(expectedUri, builder.build().getURI().toString());
+ assertEquals("DELETE", builder.getMethod());
+ }
+
+ @Test(expected = MeetingsResponseException.class)
+ public void testUnsuccessfulResponse() throws Exception {
+ endpoint.parseResponse(TestUtils.makeJsonHttpResponse(400, "{}"));
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/vonage/client/meetings/FinalizeLogosEndpointTest.java b/src/test/java/com/vonage/client/meetings/FinalizeLogosEndpointTest.java
new file mode 100644
index 000000000..411d1ec97
--- /dev/null
+++ b/src/test/java/com/vonage/client/meetings/FinalizeLogosEndpointTest.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2023 Vonage
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.vonage.client.meetings;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.vonage.client.HttpConfig;
+import com.vonage.client.HttpWrapper;
+import com.vonage.client.TestUtils;
+import com.vonage.client.VonageUnexpectedException;
+import com.vonage.client.auth.JWTAuthMethod;
+import org.apache.http.client.methods.RequestBuilder;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import org.junit.Before;
+import org.junit.Test;
+import java.util.Arrays;
+import java.util.UUID;
+
+public class FinalizeLogosEndpointTest {
+ private FinalizeLogosEndpoint endpoint;
+
+ @Before
+ public void setUp() {
+ endpoint = new FinalizeLogosEndpoint(new HttpWrapper(new JWTAuthMethod("app-id", new byte[0])));
+ }
+
+ @Test
+ public void testDefaultUri() throws Exception {
+ UUID themeId = UUID.randomUUID();
+ FinalizeLogosRequest request = new FinalizeLogosRequest(themeId, Arrays.asList("col", "fff"));
+ RequestBuilder builder = endpoint.makeRequest(request);
+ assertEquals("PUT", builder.getMethod());
+ String expectedUri = "https://api-eu.vonage.com/meetings/themes/"+themeId+"/finalizeLogos";
+ assertEquals(expectedUri, builder.build().getURI().toString());
+ assertNull(endpoint.parseResponse(TestUtils.makeJsonHttpResponse(200, "")));
+ }
+
+ @Test
+ public void testCustomUri() throws Exception {
+ UUID themeId = UUID.randomUUID();
+ String baseUri = "http://example.com";
+ HttpWrapper wrapper = new HttpWrapper(HttpConfig.builder().baseUri(baseUri).build());
+ endpoint = new FinalizeLogosEndpoint(wrapper);
+ String expectedUri = baseUri + "/meetings/themes/"+themeId+"/finalizeLogos";
+ FinalizeLogosRequest request = new FinalizeLogosRequest(themeId, Arrays.asList("lk1", "lk2"));
+ RequestBuilder builder = endpoint.makeRequest(request);
+ assertEquals(expectedUri, builder.build().getURI().toString());
+ assertEquals("PUT", builder.getMethod());
+ }
+
+ @Test(expected = MeetingsResponseException.class)
+ public void testUnsuccessfulResponse() throws Exception {
+ endpoint.parseResponse(TestUtils.makeJsonHttpResponse(400, "{}"));
+ }
+
+ @Test(expected = VonageUnexpectedException.class)
+ public void triggerJsonProcessingException() {
+ class SelfRefrencing extends FinalizeLogosRequest {
+ @JsonProperty("self") final SelfRefrencing self = this;
+ SelfRefrencing() {
+ super(null, null);
+ }
+ }
+ new SelfRefrencing().toJson();
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/vonage/client/meetings/GetLogoUploadUrlsEndpointTest.java b/src/test/java/com/vonage/client/meetings/GetLogoUploadUrlsEndpointTest.java
new file mode 100644
index 000000000..a6b85f4c0
--- /dev/null
+++ b/src/test/java/com/vonage/client/meetings/GetLogoUploadUrlsEndpointTest.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2023 Vonage
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.vonage.client.meetings;
+
+import com.vonage.client.HttpConfig;
+import com.vonage.client.HttpWrapper;
+import com.vonage.client.TestUtils;
+import com.vonage.client.VonageResponseParseException;
+import com.vonage.client.auth.JWTAuthMethod;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.RequestBuilder;
+import static org.junit.Assert.*;
+import org.junit.Before;
+import org.junit.Test;
+import java.util.List;
+
+public class GetLogoUploadUrlsEndpointTest {
+ private GetLogoUploadUrlsEndpoint endpoint;
+
+ @Before
+ public void setUp() {
+ endpoint = new GetLogoUploadUrlsEndpoint(new HttpWrapper(new JWTAuthMethod("app-id", new byte[0])));
+ }
+
+ @Test
+ public void testDefaultUri() throws Exception {
+ RequestBuilder builder = endpoint.makeRequest(null);
+ assertEquals("GET", builder.getMethod());
+ String expectedUri = "https://api-eu.vonage.com/meetings/themes/logos-upload-urls";
+ assertEquals(expectedUri, builder.build().getURI().toString());
+ }
+
+ @Test
+ public void testCustomUri() throws Exception {
+ String baseUri = "http://example.com";
+ HttpWrapper wrapper = new HttpWrapper(HttpConfig.builder().baseUri(baseUri).build());
+ endpoint = new GetLogoUploadUrlsEndpoint(wrapper);
+ String expectedUri = baseUri + "/meetings/themes/logos-upload-urls";
+ RequestBuilder builder = endpoint.makeRequest(null);
+ assertEquals(expectedUri, builder.build().getURI().toString());
+ assertEquals("GET", builder.getMethod());
+ }
+
+ @Test(expected = MeetingsResponseException.class)
+ public void testUnsuccessfulResponse() throws Exception {
+ endpoint.parseResponse(TestUtils.makeJsonHttpResponse(400, "{}"));
+ }
+
+ @Test
+ public void testEmptyEdgeCases() throws Exception {
+ String expectedResponse = "[{\"nonsense\":true}]";
+ HttpResponse mockResponse = TestUtils.makeJsonHttpResponse(200, expectedResponse);
+ List parsed = endpoint.parseResponse(mockResponse);
+ assertNotNull(parsed);
+ assertEquals(1, parsed.size());
+ assertNotNull(parsed.get(0));
+
+ expectedResponse = "[]";
+ mockResponse = TestUtils.makeJsonHttpResponse(200, expectedResponse);
+ parsed = endpoint.parseResponse(mockResponse);
+ assertNotNull(parsed);
+ assertEquals(0, parsed.size());
+
+ expectedResponse = "";
+ mockResponse = TestUtils.makeJsonHttpResponse(200, expectedResponse);
+ parsed = endpoint.parseResponse(mockResponse);
+ assertNotNull(parsed);
+ assertEquals(0, parsed.size());
+ }
+
+ @Test(expected = VonageResponseParseException.class)
+ public void testParseMalformedResponse() throws Exception {
+ endpoint.parseResponse(TestUtils.makeJsonHttpResponse(200, "{malformed]"));
+ }
+
+ @Test(expected = VonageResponseParseException.class)
+ public void testInvalidContentType() throws Exception {
+ String json = "[{\"fields\":{\"Content-Type\":\"not-a-mime\"}}]";
+ endpoint.parseResponse(TestUtils.makeJsonHttpResponse(200, json));
+ }
+
+ @Test
+ public void testInvalidLogoType() throws Exception {
+ String json = "[{\"fields\":{\"logoType\":\"smoll\"}}]";
+ HttpResponse mockResponse = TestUtils.makeJsonHttpResponse(200, json);
+ List parsed = endpoint.parseResponse(mockResponse);
+ assertNotNull(parsed);
+ assertEquals(1, parsed.size());
+ assertNull(parsed.get(0).getFields().getLogoType());
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/vonage/client/meetings/GetRecordingEndpointTest.java b/src/test/java/com/vonage/client/meetings/GetRecordingEndpointTest.java
new file mode 100644
index 000000000..0fe9ba565
--- /dev/null
+++ b/src/test/java/com/vonage/client/meetings/GetRecordingEndpointTest.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2023 Vonage
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.vonage.client.meetings;
+
+import com.vonage.client.HttpConfig;
+import com.vonage.client.HttpWrapper;
+import com.vonage.client.TestUtils;
+import com.vonage.client.VonageResponseParseException;
+import com.vonage.client.auth.JWTAuthMethod;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.RequestBuilder;
+import org.apache.http.entity.ContentType;
+import static org.junit.Assert.*;
+import org.junit.Before;
+import org.junit.Test;
+import java.util.UUID;
+
+public class GetRecordingEndpointTest {
+ private GetRecordingEndpoint endpoint;
+
+ @Before
+ public void setUp() {
+ endpoint = new GetRecordingEndpoint(new HttpWrapper(new JWTAuthMethod("app-id", new byte[0])));
+ }
+
+ @Test
+ public void testDefaultUri() throws Exception {
+ UUID recordingId = UUID.randomUUID();
+ RequestBuilder builder = endpoint.makeRequest(recordingId);
+ assertEquals("GET", builder.getMethod());
+ String expectedUri = "https://api-eu.vonage.com/meetings/recordings/"+recordingId;
+ assertEquals(expectedUri, builder.build().getURI().toString());
+ assertEquals(ContentType.APPLICATION_JSON.getMimeType(), builder.getFirstHeader("Accept").getValue());
+ String expectedResponse = "{\"nonsense\":0,\"_links\":{\"url\":{}}}";
+ HttpResponse mockResponse = TestUtils.makeJsonHttpResponse(200, expectedResponse);
+ Recording parsed = endpoint.parseResponse(mockResponse);
+ assertNotNull(parsed);
+ assertNull(parsed.getUrl());
+ assertNull(parsed.getStatus());
+ assertNull(parsed.getSessionId());
+ assertNull(parsed.getStartedAt());
+ assertNull(parsed.getEndedAt());
+ assertNull(parsed.getId());
+ }
+
+ @Test
+ public void testCustomUri() throws Exception {
+ UUID recordingId = UUID.randomUUID();
+ String baseUri = "http://example.com";
+ HttpWrapper wrapper = new HttpWrapper(HttpConfig.builder().baseUri(baseUri).build());
+ endpoint = new GetRecordingEndpoint(wrapper);
+ String expectedUri = baseUri + "/meetings/recordings/"+recordingId;
+ RequestBuilder builder = endpoint.makeRequest(recordingId);
+ assertEquals(expectedUri, builder.build().getURI().toString());
+ assertEquals(ContentType.APPLICATION_JSON.getMimeType(), builder.getFirstHeader("Accept").getValue());
+ assertEquals("GET", builder.getMethod());
+ }
+
+ @Test(expected = MeetingsResponseException.class)
+ public void testUnsuccessfulResponse() throws Exception {
+ endpoint.parseResponse(TestUtils.makeJsonHttpResponse(400, "{}"));
+ }
+
+ @Test(expected = VonageResponseParseException.class)
+ public void testParseMalformedResponse() throws Exception {
+ endpoint.parseResponse(TestUtils.makeJsonHttpResponse(200, "{malformed]"));
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/vonage/client/meetings/GetRoomEndpointTest.java b/src/test/java/com/vonage/client/meetings/GetRoomEndpointTest.java
new file mode 100644
index 000000000..8e6dbd24d
--- /dev/null
+++ b/src/test/java/com/vonage/client/meetings/GetRoomEndpointTest.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2023 Vonage
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.vonage.client.meetings;
+
+import com.vonage.client.HttpConfig;
+import com.vonage.client.HttpWrapper;
+import com.vonage.client.TestUtils;
+import com.vonage.client.auth.JWTAuthMethod;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.RequestBuilder;
+import org.apache.http.entity.ContentType;
+import static org.junit.Assert.assertEquals;
+import org.junit.Before;
+import org.junit.Test;
+import java.util.UUID;
+
+public class GetRoomEndpointTest {
+ private GetRoomEndpoint endpoint;
+
+ @Before
+ public void setUp() {
+ endpoint = new GetRoomEndpoint(new HttpWrapper(new JWTAuthMethod("app-id", new byte[0])));
+ }
+
+ @Test
+ public void testDefaultUri() throws Exception {
+ UUID roomId = UUID.randomUUID();
+ RequestBuilder builder = endpoint.makeRequest(roomId);
+ assertEquals("GET", builder.getMethod());
+ String expectedUri = "https://api-eu.vonage.com/meetings/rooms/"+roomId;
+ assertEquals(expectedUri, builder.build().getURI().toString());
+ assertEquals(ContentType.APPLICATION_JSON.getMimeType(), builder.getFirstHeader("Accept").getValue());
+ HttpResponse mockResponse = TestUtils.makeJsonHttpResponse(200, MeetingsClientTest.SAMPLE_ROOM_RESPONSE);
+ MeetingRoom parsedResponse = endpoint.parseResponse(mockResponse);
+ MeetingsClientTest.assertEqualsSampleRoom(parsedResponse);
+ }
+
+ @Test
+ public void testCustomUri() throws Exception {
+ UUID roomId = UUID.randomUUID();
+ String baseUri = "https://example.com";
+ HttpWrapper wrapper = new HttpWrapper(HttpConfig.builder().baseUri(baseUri).build());
+ endpoint = new GetRoomEndpoint(wrapper);
+ String expectedUri = baseUri + "/meetings/rooms/"+roomId;
+ RequestBuilder builder = endpoint.makeRequest(roomId);
+ assertEquals(expectedUri, builder.build().getURI().toString());
+ assertEquals(ContentType.APPLICATION_JSON.getMimeType(), builder.getFirstHeader("Accept").getValue());
+ assertEquals("GET", builder.getMethod());
+ }
+
+ @Test(expected = MeetingsResponseException.class)
+ public void testUnsuccessfulResponse() throws Exception {
+ endpoint.parseResponse(TestUtils.makeJsonHttpResponse(400, "{}"));
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/vonage/client/meetings/GetThemeEndpointTest.java b/src/test/java/com/vonage/client/meetings/GetThemeEndpointTest.java
new file mode 100644
index 000000000..1cfde6510
--- /dev/null
+++ b/src/test/java/com/vonage/client/meetings/GetThemeEndpointTest.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2023 Vonage
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.vonage.client.meetings;
+
+import com.vonage.client.HttpConfig;
+import com.vonage.client.HttpWrapper;
+import com.vonage.client.TestUtils;
+import com.vonage.client.auth.JWTAuthMethod;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.RequestBuilder;
+import org.apache.http.entity.ContentType;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import org.junit.Before;
+import org.junit.Test;
+import java.util.UUID;
+
+public class GetThemeEndpointTest {
+ private GetThemeEndpoint endpoint;
+
+ @Before
+ public void setUp() {
+ endpoint = new GetThemeEndpoint(new HttpWrapper(new JWTAuthMethod("app-id", new byte[0])));
+ }
+
+ @Test
+ public void testDefaultUri() throws Exception {
+ UUID themeId = UUID.randomUUID();
+ RequestBuilder builder = endpoint.makeRequest(themeId);
+ assertEquals("GET", builder.getMethod());
+ String expectedUri = "https://api-eu.vonage.com/meetings/themes/"+themeId;
+ assertEquals(expectedUri, builder.build().getURI().toString());
+ assertEquals(ContentType.APPLICATION_JSON.getMimeType(), builder.getFirstHeader("Accept").getValue());
+ String expectedResponse = "{\"nonsense\":true}";
+ HttpResponse mockResponse = TestUtils.makeJsonHttpResponse(200, expectedResponse);
+ Theme parsed = endpoint.parseResponse(mockResponse);
+ assertNotNull(parsed);
+ }
+
+ @Test
+ public void testCustomUri() throws Exception {
+ String baseUri = "http://example.com";
+ HttpWrapper wrapper = new HttpWrapper(HttpConfig.builder().baseUri(baseUri).build());
+ endpoint = new GetThemeEndpoint(wrapper);
+ UUID themeId = UUID.randomUUID();
+ String expectedUri = baseUri + "/meetings/themes/"+themeId;
+ RequestBuilder builder = endpoint.makeRequest(themeId);
+ assertEquals(expectedUri, builder.build().getURI().toString());
+ assertEquals(ContentType.APPLICATION_JSON.getMimeType(), builder.getFirstHeader("Accept").getValue());
+ assertEquals("GET", builder.getMethod());
+ }
+
+ @Test(expected = MeetingsResponseException.class)
+ public void testUnsuccessfulResponse() throws Exception {
+ endpoint.parseResponse(TestUtils.makeJsonHttpResponse(400, "{}"));
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/vonage/client/meetings/ListDialNumbersEndpointTest.java b/src/test/java/com/vonage/client/meetings/ListDialNumbersEndpointTest.java
new file mode 100644
index 000000000..bc8605c0b
--- /dev/null
+++ b/src/test/java/com/vonage/client/meetings/ListDialNumbersEndpointTest.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2023 Vonage
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.vonage.client.meetings;
+
+import com.vonage.client.HttpConfig;
+import com.vonage.client.HttpWrapper;
+import com.vonage.client.TestUtils;
+import com.vonage.client.auth.JWTAuthMethod;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.RequestBuilder;
+import static org.junit.Assert.*;
+import org.junit.Before;
+import org.junit.Test;
+import java.util.List;
+import java.util.Locale;
+
+public class ListDialNumbersEndpointTest {
+ private ListDialNumbersEndpoint endpoint;
+
+ @Before
+ public void setUp() {
+ endpoint = new ListDialNumbersEndpoint(new HttpWrapper(new JWTAuthMethod("app-id", new byte[0])));
+ }
+
+ @Test
+ public void testDefaultUri() throws Exception {
+ RequestBuilder builder = endpoint.makeRequest(null);
+ assertEquals("GET", builder.getMethod());
+ String expectedUri = "https://api-eu.vonage.com/meetings/dial-in-numbers";
+ assertEquals(expectedUri, builder.build().getURI().toString());
+ String expectedPayload = "[\n" +
+ "{}, {\n" +
+ " \"number\": \"17323338801\",\n" +
+ " \"locale\": \"en-US\",\n" +
+ " \"display_name\": \"United States\"\n" +
+ " }," +
+ " {\"locale\": \"de-DE\",\"unknown property\":0}\n" +
+ "]";
+ HttpResponse mockResponse = TestUtils.makeJsonHttpResponse(200, expectedPayload);
+ List parsed = endpoint.parseResponse(mockResponse);
+ assertEquals(3, parsed.size());
+
+ DialInNumber empty = parsed.get(0);
+ assertNotNull(empty);
+ assertNull(empty.getNumber());
+ assertNull(empty.getDisplayName());
+ assertNull(empty.getLocale());
+
+ DialInNumber usa = parsed.get(1);
+ assertEquals("17323338801", usa.getNumber());
+ assertEquals("United States", usa.getDisplayName());
+ assertEquals(Locale.US, usa.getLocale());
+
+ DialInNumber germany = parsed.get(2);
+ assertEquals(Locale.GERMANY, germany.getLocale());
+ assertNull(germany.getDisplayName());
+ assertNull(germany.getNumber());
+ }
+
+ @Test
+ public void testCustomUri() throws Exception {
+ String baseUri = "http://example.com";
+ HttpWrapper wrapper = new HttpWrapper(HttpConfig.builder().baseUri(baseUri).build());
+ endpoint = new ListDialNumbersEndpoint(wrapper);
+ String expectedUri = baseUri + "/meetings/dial-in-numbers";
+ RequestBuilder builder = endpoint.makeRequest(null);
+ assertEquals(expectedUri, builder.build().getURI().toString());
+ assertEquals("GET", builder.getMethod());
+ }
+
+ @Test(expected = MeetingsResponseException.class)
+ public void testUnsuccessfulResponse() throws Exception {
+ endpoint.parseResponse(TestUtils.makeJsonHttpResponse(400, "{}"));
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/vonage/client/meetings/ListRecordingsEndpointTest.java b/src/test/java/com/vonage/client/meetings/ListRecordingsEndpointTest.java
new file mode 100644
index 000000000..ebbe0a3c9
--- /dev/null
+++ b/src/test/java/com/vonage/client/meetings/ListRecordingsEndpointTest.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2023 Vonage
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.vonage.client.meetings;
+
+import com.vonage.client.HttpConfig;
+import com.vonage.client.HttpWrapper;
+import com.vonage.client.TestUtils;
+import com.vonage.client.VonageResponseParseException;
+import com.vonage.client.auth.JWTAuthMethod;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.RequestBuilder;
+import org.apache.http.entity.ContentType;
+import static org.junit.Assert.*;
+import org.junit.Before;
+import org.junit.Test;
+
+public class ListRecordingsEndpointTest {
+ private ListRecordingsEndpoint endpoint;
+
+ @Before
+ public void setUp() {
+ endpoint = new ListRecordingsEndpoint(new HttpWrapper(new JWTAuthMethod("app-id", new byte[0])));
+ }
+
+ @Test
+ public void testMakeRequest() throws Exception {
+ String sessionId = "2_MX40NjMwODczMn5-MTU3NTgyODEwNzQ2M...";
+ RequestBuilder builder = endpoint.makeRequest(sessionId);
+ assertEquals("GET", builder.getMethod());
+ String expectedUri = "https://api-eu.vonage.com/meetings/sessions/"+sessionId+"/recordings";
+ assertEquals(expectedUri, builder.build().getURI().toString());
+ assertEquals(ContentType.APPLICATION_JSON.getMimeType(), builder.getFirstHeader("Accept").getValue());
+ String expectedPayload = "{\"_embedded\":{\"recordings\":[{},{\"_links\":{},\"status\":\"uploaded\"}]}}";
+ HttpResponse mockResponse = TestUtils.makeJsonHttpResponse(200, expectedPayload);
+ ListRecordingsResponse parsed = endpoint.parseResponse(mockResponse);
+ assertEquals(2, parsed.getRecordings().size());
+ assertNull(parsed.getRecordings().get(1).getUrl());
+ assertEquals(RecordingStatus.UPLOADED, parsed.getRecordings().get(1).getStatus());
+ assertEquals("uploaded", RecordingStatus.UPLOADED.toString());
+ Recording empty = parsed.getRecordings().get(0);
+ assertNotNull(empty);
+ assertNull(empty.getId());
+ assertNull(empty.getUrl());
+ assertNull(empty.getEndedAt());
+ assertNull(empty.getStartedAt());
+ assertNull(empty.getStatus());
+ assertNull(empty.getSessionId());
+ }
+
+ @Test
+ public void testCustomUri() throws Exception {
+ String sessionId = "2_MX40NjMwODczMn5-MTU3NTgyODEwNzQ2Jk.." , baseUri = "http://example.com";
+ HttpWrapper wrapper = new HttpWrapper(HttpConfig.builder().baseUri(baseUri).build());
+ endpoint = new ListRecordingsEndpoint(wrapper);
+ String expectedUri = baseUri + "/meetings/sessions/"+sessionId+"/recordings";
+ RequestBuilder builder = endpoint.makeRequest(sessionId);
+ assertEquals(expectedUri, builder.build().getURI().toString());
+ assertEquals(ContentType.APPLICATION_JSON.getMimeType(), builder.getFirstHeader("Accept").getValue());
+ assertEquals("GET", builder.getMethod());
+ }
+
+ @Test(expected = MeetingsResponseException.class)
+ public void testUnsuccessfulResponse() throws Exception {
+ endpoint.parseResponse(TestUtils.makeJsonHttpResponse(400, "{}"));
+ }
+
+ @Test
+ public void testEmptyEdgeCases() throws Exception {
+ HttpResponse mockResponse = TestUtils.makeJsonHttpResponse(200, "");
+ ListRecordingsResponse parsed = endpoint.parseResponse(mockResponse);
+ assertNotNull(parsed);
+ assertNull(parsed.getRecordings());
+
+ mockResponse = TestUtils.makeJsonHttpResponse(200, "{}");
+ parsed = endpoint.parseResponse(mockResponse);
+ assertNotNull(parsed);
+ assertNull(parsed.getRecordings());
+
+ mockResponse = TestUtils.makeJsonHttpResponse(200, "{\"_embedded\":{}}");
+ parsed = endpoint.parseResponse(mockResponse);
+ assertNotNull(parsed);
+ assertNull(parsed.getRecordings());
+
+ mockResponse = TestUtils.makeJsonHttpResponse(200, "{\"_embedded\":{\"recordings\":[]}}");
+ parsed = endpoint.parseResponse(mockResponse);
+ assertNotNull(parsed);
+ assertNotNull(parsed.getRecordings());
+ assertEquals(0, parsed.getRecordings().size());
+
+ mockResponse = TestUtils.makeJsonHttpResponse(200, "{\"_embedded\":{\"recordings\":[{}]}}");
+ parsed = endpoint.parseResponse(mockResponse);
+ assertNotNull(parsed);
+ assertNotNull(parsed.getRecordings());
+ assertEquals(1, parsed.getRecordings().size());
+ assertNotNull(parsed.getRecordings().get(0));
+ }
+
+ @Test
+ public void testInvalidRecordingStatus() throws Exception {
+ String expectedPayload = "{\"_embedded\":{\"recordings\":[{\"status\":\"You're on camera!\"}]}}";
+ HttpResponse mockResponse = TestUtils.makeJsonHttpResponse(200, expectedPayload);
+ ListRecordingsResponse parsed = endpoint.parseResponse(mockResponse);
+ assertNotNull(parsed);
+ assertEquals(1, parsed.getRecordings().size());
+ assertNull(parsed.getRecordings().get(0).getStatus());
+ }
+
+ @Test(expected = VonageResponseParseException.class)
+ public void testParseMalformedResponse() throws Exception {
+ endpoint.parseResponse(TestUtils.makeJsonHttpResponse(200, "{malformed]"));
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/vonage/client/meetings/ListRoomsEndpointTest.java b/src/test/java/com/vonage/client/meetings/ListRoomsEndpointTest.java
new file mode 100644
index 000000000..63c58cd49
--- /dev/null
+++ b/src/test/java/com/vonage/client/meetings/ListRoomsEndpointTest.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2023 Vonage
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.vonage.client.meetings;
+
+import com.vonage.client.HttpConfig;
+import com.vonage.client.HttpWrapper;
+import com.vonage.client.TestUtils;
+import com.vonage.client.VonageResponseParseException;
+import com.vonage.client.auth.JWTAuthMethod;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.RequestBuilder;
+import org.apache.http.entity.ContentType;
+import static org.junit.Assert.*;
+import org.junit.Before;
+import org.junit.Test;
+import java.util.Map;
+
+public class ListRoomsEndpointTest {
+ private ListRoomsEndpoint endpoint;
+
+ @Before
+ public void setUp() {
+ endpoint = new ListRoomsEndpoint(new HttpWrapper(new JWTAuthMethod("app-id", new byte[0])));
+ }
+
+ @Test
+ public void testDefaultUri() throws Exception {
+ ListRoomsRequest request = new ListRoomsRequest(null, null, null, null);
+ RequestBuilder builder = endpoint.makeRequest(request);
+ assertEquals("GET", builder.getMethod());
+ String expectedUri = "https://api-eu.vonage.com/meetings/rooms";
+ assertEquals(expectedUri, builder.build().getURI().toString());
+ Map params = TestUtils.makeParameterMap(builder.getParameters());
+ assertEquals(0, params.size());
+ assertEquals(ContentType.APPLICATION_JSON.getMimeType(), builder.getFirstHeader("Accept").getValue());
+ String expectedResponse = "{\n" +
+ "\"page_size\":"+request.pageSize+",\"_embedded\":[{}],\"total_items\":3}";
+ HttpResponse mockResponse = TestUtils.makeJsonHttpResponse(200, expectedResponse);
+ ListRoomsResponse parsed = endpoint.parseResponse(mockResponse);
+ assertNull(parsed.getLinks());
+ assertEquals(1, parsed.getMeetingRooms().size());
+ assertNotNull(parsed.getMeetingRooms().get(0));
+ assertEquals(3, parsed.getTotalItems().intValue());
+ }
+
+ @Test
+ public void testCustomUri() throws Exception {
+ String baseUri = "http://example.com";
+ HttpWrapper wrapper = new HttpWrapper(HttpConfig.builder().baseUri(baseUri).build());
+ endpoint = new ListRoomsEndpoint(wrapper);
+ ListRoomsRequest request = new ListRoomsRequest(51, 150, 25, null);
+ String expectedUri = baseUri + "/meetings/rooms?" +
+ "start_id="+request.startId+"&end_id="+request.endId+"&page_size="+request.pageSize;
+ RequestBuilder builder = endpoint.makeRequest(request);
+ assertEquals(expectedUri, builder.build().getURI().toString());
+ Map params = TestUtils.makeParameterMap(builder.getParameters());
+ assertEquals(3, params.size());
+ assertEquals(ContentType.APPLICATION_JSON.getMimeType(), builder.getFirstHeader("Accept").getValue());
+ assertEquals("GET", builder.getMethod());
+ }
+
+ @Test(expected = MeetingsResponseException.class)
+ public void testUnsuccessfulResponse() throws Exception {
+ endpoint.parseResponse(TestUtils.makeJsonHttpResponse(400, "{}"));
+ }
+
+ @Test(expected = VonageResponseParseException.class)
+ public void testParseMalformedResponse() throws Exception {
+ endpoint.parseResponse(TestUtils.makeJsonHttpResponse(200, "{malformed]"));
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/vonage/client/meetings/ListThemesEndpointTest.java b/src/test/java/com/vonage/client/meetings/ListThemesEndpointTest.java
new file mode 100644
index 000000000..85841f7a8
--- /dev/null
+++ b/src/test/java/com/vonage/client/meetings/ListThemesEndpointTest.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2023 Vonage
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.vonage.client.meetings;
+
+import com.vonage.client.HttpConfig;
+import com.vonage.client.HttpWrapper;
+import com.vonage.client.TestUtils;
+import com.vonage.client.auth.JWTAuthMethod;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.RequestBuilder;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import org.junit.Before;
+import org.junit.Test;
+import java.util.List;
+
+public class ListThemesEndpointTest {
+ private ListThemesEndpoint endpoint;
+
+ @Before
+ public void setUp() {
+ endpoint = new ListThemesEndpoint(new HttpWrapper(new JWTAuthMethod("app-id", new byte[0])));
+ }
+
+ @Test
+ public void testDefaultUri() throws Exception {
+ Void request = null;
+ RequestBuilder builder = endpoint.makeRequest(request);
+ assertEquals("GET", builder.getMethod());
+ String expectedUri = "https://api-eu.vonage.com/meetings/themes";
+ assertEquals(expectedUri, builder.build().getURI().toString());
+ HttpResponse mockResponse = TestUtils.makeJsonHttpResponse(200, "[{}]");
+ List parsed = endpoint.parseResponse(mockResponse);
+ assertEquals(1, parsed.size());
+ assertNotNull(parsed.get(0));
+ }
+
+ @Test
+ public void testCustomUri() throws Exception {
+ String baseUri = "http://example.com";
+ HttpWrapper wrapper = new HttpWrapper(HttpConfig.builder().baseUri(baseUri).build());
+ endpoint = new ListThemesEndpoint(wrapper);
+ String expectedUri = baseUri + "/meetings/themes";
+ RequestBuilder builder = endpoint.makeRequest(null);
+ assertEquals(expectedUri, builder.build().getURI().toString());
+ assertEquals("GET", builder.getMethod());
+ }
+
+ @Test(expected = MeetingsResponseException.class)
+ public void testUnsuccessfulResponse() throws Exception {
+ endpoint.parseResponse(TestUtils.makeJsonHttpResponse(400, "{}"));
+ }
+
+ @Test
+ public void testEmptyStringReturnsEmptyList() throws Exception {
+ HttpResponse mockResponse = TestUtils.makeJsonHttpResponse(200, "");
+ List parsed = endpoint.parseResponse(mockResponse);
+ assertNotNull(parsed);
+ assertEquals(0, parsed.size());
+ mockResponse = TestUtils.makeJsonHttpResponse(200, "[]");
+ parsed = endpoint.parseResponse(mockResponse);
+ assertNotNull(parsed);
+ assertEquals(0, parsed.size());
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/vonage/client/meetings/MeetingRoomTest.java b/src/test/java/com/vonage/client/meetings/MeetingRoomTest.java
new file mode 100644
index 000000000..90ff9bd05
--- /dev/null
+++ b/src/test/java/com/vonage/client/meetings/MeetingRoomTest.java
@@ -0,0 +1,343 @@
+/*
+ * Copyright 2023 Vonage
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.vonage.client.meetings;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.vonage.client.VonageResponseParseException;
+import com.vonage.client.VonageUnexpectedException;
+import static org.junit.Assert.*;
+import org.junit.Test;
+import java.net.URI;
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
+import java.util.UUID;
+
+public class MeetingRoomTest {
+
+ @Test
+ public void testSerializeAndParseAllParameters() {
+ String
+ displayName = "Sample room",
+ metadata = "var=val",
+ roomsCallbackUrl = "http://example.org/roomsCb",
+ recordingsCallbackUrl = "http://example.org/recordingsCb",
+ sessionsCallbackUrl = "http://example.org/sessionsCb";
+ RoomType type = RoomType.LONG_TERM;
+ RoomLanguage language = RoomLanguage.EN;
+ MicrophoneState microphoneState = MicrophoneState.ON;
+ JoinApprovalLevel joinApprovalLevel = JoinApprovalLevel.EXPLICIT_APPROVAL;
+ boolean
+ isAvailable = false,
+ expireAfterUse = true,
+ autoRecord = true,
+ recordOnlyOwner = false,
+ isChatAvailable = true,
+ isLocaleSwitcherAvailable = false,
+ isRecordingAvailable = true,
+ isWhiteboardAvailable = false;
+ RecordingOptions recordingOptions = RecordingOptions.builder()
+ .autoRecord(autoRecord).recordOnlyOwner(recordOnlyOwner).build();
+ InitialJoinOptions initialJoinOptions = InitialJoinOptions.builder()
+ .microphoneState(microphoneState).build();
+ CallbackUrls callbackUrls = CallbackUrls.builder()
+ .roomsCallbackUrl(roomsCallbackUrl)
+ .recordingsCallbackUrl(recordingsCallbackUrl)
+ .sessionsCallbackUrl(sessionsCallbackUrl).build();
+ AvailableFeatures availableFeatures = AvailableFeatures.builder()
+ .isChatAvailable(isChatAvailable)
+ .isLocaleSwitcherAvailable(isLocaleSwitcherAvailable)
+ .isRecordingAvailable(isRecordingAvailable)
+ .isWhiteboardAvailable(isWhiteboardAvailable).build();
+ UUID themeId = UUID.randomUUID();
+ Instant expiresAt = Instant.now().plusSeconds(3600);
+ UISettings uiSettings = UISettings.builder().language(language).build();
+
+ MeetingRoom request = MeetingRoom.builder(displayName)
+ .metadata(metadata)
+ .type(type)
+ .isAvailable(isAvailable)
+ .expireAfterUse(expireAfterUse)
+ .recordingOptions(recordingOptions)
+ .initialJoinOptions(initialJoinOptions)
+ .callbackUrls(callbackUrls)
+ .availableFeatures(availableFeatures)
+ .themeId(themeId)
+ .joinApprovalLevel(joinApprovalLevel)
+ .expiresAt(expiresAt)
+ .uiSettings(uiSettings)
+ .build();
+
+ String json = request.toJson();
+ assertTrue(json.contains("\"display_name\":\""+displayName+"\""));
+ assertTrue(json.contains("\"metadata\":\""+metadata+"\""));
+ assertTrue(json.contains("\"type\":\""+type+"\""));
+ assertTrue(json.contains("\"is_available\":"+isAvailable));
+ assertTrue(json.contains("\"expire_after_use\":"+expireAfterUse));
+ assertTrue(json.contains("\"recording_options\":{" +
+ "\"auto_record\":"+autoRecord +
+ ",\"record_only_owner\":"+recordOnlyOwner+"}"
+ ));
+ assertTrue(json.contains("\"callback_urls\":{" +
+ "\"rooms_callback_url\":\""+roomsCallbackUrl +
+ "\",\"sessions_callback_url\":\""+sessionsCallbackUrl +
+ "\",\"recordings_callback_url\":\""+recordingsCallbackUrl+"\"}"
+ ));
+ assertTrue(json.contains("\"available_features\":{" +
+ "\"is_recording_available\":"+isRecordingAvailable +
+ ",\"is_chat_available\":"+isChatAvailable +
+ ",\"is_whiteboard_available\":"+isWhiteboardAvailable +
+ ",\"is_locale_switcher_available\":"+isLocaleSwitcherAvailable+"}"
+ ));
+ assertTrue(json.contains("\"initial_join_options\":{\"microphone_state\":\""+microphoneState+"\"}"));
+ assertTrue(json.contains("\"ui_settings\":{\"language\":\""+language+"\"}"));
+ assertTrue(json.contains("\"theme_id\":\""+themeId+"\""));
+ assertTrue(json.contains("\"join_approval_level\":\""+joinApprovalLevel+"\""));
+ assertTrue(json.contains("\"expires_at\":\""+request.getExpiresAt()+"\""));
+
+ MeetingRoom response = MeetingRoom.fromJson(json);
+ assertEquals(request.getDisplayName(), response.getDisplayName());
+ assertEquals(request.getMetadata(), response.getMetadata());
+ assertEquals(request.getType(), response.getType());
+ assertEquals(request.getIsAvailable(), response.getIsAvailable());
+ assertEquals(request.getExpireAfterUse(), response.getExpireAfterUse());
+ assertEquals(request.getThemeId(), response.getThemeId());
+ assertEquals(request.getJoinApprovalLevel(), response.getJoinApprovalLevel());
+ assertEquals(request.getExpiresAt(), response.getExpiresAt());
+
+ RecordingOptions responseRecOpts = response.getRecordingOptions();
+ assertEquals(autoRecord, responseRecOpts.getAutoRecord());
+ assertEquals(recordOnlyOwner, responseRecOpts.getRecordOnlyOwner());
+
+ InitialJoinOptions responseJoinOpts = response.getInitialJoinOptions();
+ assertEquals(microphoneState, responseJoinOpts.getMicrophoneState());
+
+ UISettings responseUi = response.getUiSettings();
+ assertEquals(language, responseUi.getLanguage());
+
+ AvailableFeatures responseFeatures = response.getAvailableFeatures();
+ assertEquals(isChatAvailable, responseFeatures.getIsChatAvailable());
+ assertEquals(isLocaleSwitcherAvailable, responseFeatures.getIsLocaleSwitcherAvailable());
+ assertEquals(isRecordingAvailable, responseFeatures.getIsRecordingAvailable());
+ assertEquals(isWhiteboardAvailable, responseFeatures.getIsWhiteboardAvailable());
+
+ CallbackUrls responseCallbacks = response.getCallbackUrls();
+ assertEquals(roomsCallbackUrl, responseCallbacks.getRoomsCallbackUrl().toString());
+ assertEquals(recordingsCallbackUrl, responseCallbacks.getRecordingsCallbackUrl().toString());
+ assertEquals(sessionsCallbackUrl, responseCallbacks.getSessionsCallbackUrl().toString());
+ }
+
+ @Test
+ public void testUpdateFromJson() {
+ MeetingRoom room = MeetingRoom.builder("Room name 0")
+ .expiresAt(Instant.now().plusSeconds(7200))
+ .type(RoomType.LONG_TERM).expireAfterUse(true)
+ .availableFeatures(AvailableFeatures.builder()
+ .isChatAvailable(false).isRecordingAvailable(true).build()
+ )
+ .joinApprovalLevel(JoinApprovalLevel.NONE).build();
+
+ assertNull(room.getInitialJoinOptions());
+ assertNull(room.getUiSettings());
+ assertNull(room.getIsAvailable());
+ assertNull(room.getCallbackUrls());
+ assertFalse(room.getAvailableFeatures().getIsChatAvailable());
+ assertTrue(room.getAvailableFeatures().getIsRecordingAvailable());
+ assertNull(room.getAvailableFeatures().getIsLocaleSwitcherAvailable());
+ assertNull(room.getAvailableFeatures().getIsWhiteboardAvailable());
+
+ room.updateFromJson("{\"name\":\"Updated name 1\",\"join_approval_level\":\"after_owner_only\"," +
+ "\"is_available\":true,\"type\":\"instant\",\"ui_settings\":{\"language\":\"ca\"}," +
+ "\"expire_after_use\":false,\"initial_join_options\":{},\"callback_urls\":{}," +
+ "\"available_features\":{\"is_locale_switcher_available\":false,\"is_chat_available\":false}}"
+ );
+
+ assertTrue(room.getIsAvailable());
+ assertFalse(room.getExpireAfterUse());
+ assertNotNull(room.getInitialJoinOptions());
+ assertNotNull(room.getCallbackUrls());
+ assertEquals(RoomLanguage.CA, room.getUiSettings().getLanguage());
+ assertEquals(RoomType.INSTANT, room.getType());
+ assertEquals(JoinApprovalLevel.AFTER_OWNER_ONLY, room.getJoinApprovalLevel());
+ assertFalse(room.getAvailableFeatures().getIsLocaleSwitcherAvailable());
+ assertFalse(room.getAvailableFeatures().getIsChatAvailable());
+ assertNull(room.getAvailableFeatures().getIsRecordingAvailable());
+ assertNull(room.getAvailableFeatures().getIsWhiteboardAvailable());
+
+ assertThrows(VonageResponseParseException.class, () -> room.updateFromJson("{malformed]"));
+ }
+
+ @Test
+ public void testNullOrEmptyDisplayName() {
+ assertThrows(IllegalArgumentException.class, () -> MeetingRoom.builder(null).build());
+ assertThrows(IllegalArgumentException.class, () -> MeetingRoom.builder(" ").build());
+ }
+
+ @Test
+ public void testExpireAfterUseForInstantRoomType() {
+ assertThrows(IllegalStateException.class, () ->
+ MeetingRoom.builder("A room")
+ .expireAfterUse(false)
+ .type(RoomType.INSTANT)
+ .build()
+ );
+ assertThrows(IllegalStateException.class, () ->
+ MeetingRoom.builder("A room")
+ .expireAfterUse(true)
+ .type(RoomType.INSTANT)
+ .build()
+ );
+ }
+
+ @Test
+ public void testExpiresAtAndRoomTypeValidation() {
+ Instant expire = Instant.now().plusSeconds(10_000);
+ assertNull(MeetingRoom.builder("My Room").expiresAt(expire).build().getType());
+ assertEquals(expire.truncatedTo(ChronoUnit.MILLIS),
+ MeetingRoom.builder("r").type(RoomType.LONG_TERM).expiresAt(expire).build()
+ .getExpiresAt()
+ );
+ assertThrows(IllegalStateException.class, () ->
+ MeetingRoom.builder("My Room").expiresAt(expire).type(RoomType.INSTANT).build()
+ );
+ assertThrows(IllegalStateException.class, () ->
+ MeetingRoom.builder("My Room").type(RoomType.LONG_TERM).build()
+ );
+ }
+
+ @Test
+ public void testExpiresAtLeast10MinutesFromNow() {
+ assertThrows(IllegalArgumentException.class, () -> MeetingRoom.builder("My Room")
+ .expiresAt(Instant.now().plusSeconds(599)).build()
+ );
+ assertNotNull(
+ MeetingRoom.builder("My Room")
+ .expiresAt(Instant.now().plusSeconds(601))
+ .build()
+ .getExpiresAt()
+ );
+ }
+
+ @Test
+ public void testFromJsonResponseOnlyFields() {
+ UUID id = UUID.randomUUID();
+ String meetingCode = "9876543201";
+ String guestUrl = "https://meetings.vonage.com/"+meetingCode;
+ String hostUrl = guestUrl + "?participant_token=xyz";
+ Instant createdAt = Instant.now();
+ RoomLinks links = new RoomLinks();
+ (links.guestUrl = new UrlContainer()).href = URI.create(guestUrl);
+ (links.hostUrl = new UrlContainer()).href = URI.create(hostUrl);
+
+ MeetingRoom response = MeetingRoom.fromJson("{\n" +
+ "\"id\":\""+id+"\",\n" +
+ "\"meeting_code\":\""+meetingCode+"\",\n" +
+ "\"created_at\":\""+createdAt+"\",\n" +
+ "\"_links\": {\n" +
+ " \"guest_url\": {\n" +
+ " \"href\":\""+guestUrl+"\"\n" +
+ " },\n" +
+ " \"host_url\": {\n"+
+ " \"href\":\""+hostUrl+"\"\n" +
+ " }\n" +
+ "}}"
+ );
+ assertEquals(id, response.getId());
+ assertEquals(guestUrl, response.getLinks().getGuestUrl().toString());
+ assertEquals(hostUrl, response.getLinks().getHostUrl().toString());
+ assertEquals(meetingCode, response.getMeetingCode());
+ }
+
+ @Test
+ public void testParseEmptyContainers() {
+ MeetingRoom response = MeetingRoom.fromJson("{\n" +
+ "\"initial_join_options\":{},\n" +
+ "\"recording_options\":{},\n" +
+ "\"available_features\":{},\n" +
+ "\"ui_settings\":{},\n" +
+ "\"callback_urls\":{},\n" +
+ "\"_links\":{}\n" +
+ "}");
+
+ assertNotNull(response.getInitialJoinOptions());
+ assertNull(response.getInitialJoinOptions().getMicrophoneState());
+ assertNotNull(response.getRecordingOptions());
+ assertNull(response.getRecordingOptions().getAutoRecord());
+ assertNull(response.getRecordingOptions().getRecordOnlyOwner());
+ assertNotNull(response.getAvailableFeatures());
+ assertNull(response.getAvailableFeatures().getIsLocaleSwitcherAvailable());
+ assertNull(response.getAvailableFeatures().getIsWhiteboardAvailable());
+ assertNull(response.getAvailableFeatures().getIsRecordingAvailable());
+ assertNull(response.getAvailableFeatures().getIsChatAvailable());
+ assertNotNull(response.getUiSettings());
+ assertNull(response.getUiSettings().getLanguage());
+ assertNotNull(response.getCallbackUrls());
+ assertNull(response.getCallbackUrls().getRecordingsCallbackUrl());
+ assertNull(response.getCallbackUrls().getRoomsCallbackUrl());
+ assertNull(response.getCallbackUrls().getSessionsCallbackUrl());
+ assertNotNull(response.getLinks());
+ assertNull(response.getLinks().getGuestUrl());
+ assertNull(response.getLinks().getHostUrl());
+ }
+
+ @Test(expected = VonageUnexpectedException.class)
+ public void testFromJsonInvalid() {
+ MeetingRoom.fromJson("{malformed]");
+ }
+
+ @Test
+ public void testFromJsonEmpty() {
+ MeetingRoom response = MeetingRoom.fromJson("{}");
+ assertNull(response.getDisplayName());
+ assertNull(response.getMetadata());
+ assertNull(response.getType());
+ assertNull(response.getIsAvailable());
+ assertNull(response.getExpireAfterUse());
+ assertNull(response.getRecordingOptions());
+ assertNull(response.getInitialJoinOptions());
+ assertNull(response.getCallbackUrls());
+ assertNull(response.getAvailableFeatures());
+ assertNull(response.getThemeId());
+ assertNull(response.getJoinApprovalLevel());
+ assertNull(response.getCreatedAt());
+ assertNull(response.getExpiresAt());
+ assertNull(response.getId());
+ assertNull(response.getLinks());
+ assertNull(response.getMeetingCode());
+ assertNull(response.getUiSettings());
+ }
+
+ @Test
+ public void testInvalidEnums() {
+ MeetingRoom room = MeetingRoom.fromJson(
+ "{\"join_approval_level\":\"bar\"," +
+ "\"initial_join_options\":{\"microphone_state\":\"bombastic\"}," +
+ "\"type\":\"casual\"," +
+ "\"ui_settings\":{\"language\":\"yoda\"}}"
+ );
+ assertNull(room.getJoinApprovalLevel());
+ assertNull(room.getInitialJoinOptions().getMicrophoneState());
+ assertNull(room.getType());
+ assertNull(room.getUiSettings().getLanguage());
+ }
+
+ @Test(expected = VonageUnexpectedException.class)
+ public void triggerJsonProcessingException() {
+ class SelfRefrencing extends MeetingRoom {
+ @JsonProperty("self") final SelfRefrencing self = this;
+ }
+ new SelfRefrencing().toJson();
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/vonage/client/meetings/MeetingsClientTest.java b/src/test/java/com/vonage/client/meetings/MeetingsClientTest.java
new file mode 100644
index 000000000..3d4f74018
--- /dev/null
+++ b/src/test/java/com/vonage/client/meetings/MeetingsClientTest.java
@@ -0,0 +1,786 @@
+/*
+ * Copyright 2023 Vonage
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.vonage.client.meetings;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.vonage.client.ClientTest;
+import com.vonage.client.VonageUnexpectedException;
+import com.vonage.client.common.HalLinks;
+import org.apache.http.client.HttpClient;
+import static org.junit.Assert.*;
+import org.junit.Test;
+import org.junit.function.ThrowingRunnable;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.when;
+import java.io.IOException;
+import java.net.URI;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.time.temporal.ChronoUnit;
+import java.util.*;
+import java.util.function.Supplier;
+
+public class MeetingsClientTest extends ClientTest {
+
+ static final UUID
+ RANDOM_ID = UUID.randomUUID(),
+ ROOM_ID = UUID.fromString("b84cc862-0764-4887-9265-37e8a863164d");
+ static final String
+ SAMPLE_ROOM_RESPONSE = " {\n" +
+ " \"id\": \""+ROOM_ID+"\",\n" +
+ " \"display_name\": \"Sina's room\",\n" +
+ " \"metadata\": \"foo=bar\",\n" +
+ " \"type\": \"long_term\",\n" +
+ " \"expires_at\": \"3000-01-17T15:53:03.377Z\",\n" +
+ " \"recording_options\": {\n" +
+ " \"auto_record\": false,\n" +
+ " \"record_only_owner\": false\n" +
+ " },\n" +
+ " \"meeting_code\": \"365658578\",\n" +
+ " \"_links\": {\n" +
+ " \"host_url\": {\n" +
+ " \"href\": \"https://meetings.vonage.com/?room_token=365658578&participant_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjU5N2NmYTAzLTY3NTQtNGE0ZC1hYjU1LWZiMTdkNzc4NzRjMSJ9.eyJwYXJ0aWNpcGFudElkIjoiYTc1NmI0ZGYtYjk3YS00N2JhLThiYjEtYjcyNzA5ZjNhOGFkIiwiaWF0IjoxNjc0MDcyNDM4fQ.8HbvDfIdPIsz9PFWZZM8psPNu5nZziuh0yBeHXkJoDI\"\n" +
+ " },\n" +
+ " \"guest_url\": {\n" +
+ " \"href\": \"https://meetings.vonage.com/365658578\"\n" +
+ " }\n" +
+ " },\n" +
+ " \"created_at\": \"2023-01-17T16:19:13.518Z\",\n" +
+ " \"is_available\": true,\n" +
+ " \"expire_after_use\": false,\n" +
+ " \"theme_id\": null,\n" +
+ " \"initial_join_options\": {\n" +
+ " \"microphone_state\": \"off\"\n" +
+ " },\n" +
+ " \"join_approval_level\": \"none\",\n" +
+ " \"callback_urls\": {\n" +
+ " \"rooms_callback_url\": \"https://example.com/rooms\",\n" +
+ " \"sessions_callback_url\": \"https://example.com/sessions\",\n" +
+ " \"recordings_callback_url\": \"https://example.com/recordings\"\n" +
+ " },\n" +
+ " \"ui_settings\": {\n" +
+ " \"language\": \"de\"\n" +
+ " },\n" +
+ " \"available_features\": {\n" +
+ " \"is_recording_available\": true,\n" +
+ " \"is_chat_available\": true,\n" +
+ " \"is_whiteboard_available\": true,\n" +
+ " \"is_locale_switcher_available\": true\n" +
+ " }\n" +
+ " }",
+
+ LIST_ROOMS_RESPONSE = "{\n" +
+ " \"page_size\": \"3\",\n" +
+ " \"_embedded\": [\n" + SAMPLE_ROOM_RESPONSE +",\n" +
+ " {\n" +
+ " \"id\": \"2f63e54b-adc1-4dda-a27c-24f04c0f1233\",\n" +
+ " \"display_name\": \"Manchucks\",\n" +
+ " \"metadata\": null,\n" +
+ " \"type\": \"instant\",\n" +
+ " \"recording_options\": {\n" +
+ " \"auto_record\": false,\n" +
+ " \"record_only_owner\": false\n" +
+ " },\n" +
+ " \"meeting_code\": \"710363620\",\n" +
+ " \"_links\": {\n" +
+ " \"host_url\": {\n" +
+ " \"href\": \"https://meetings.vonage.com/?room_token=710363620&participant_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjU5N2NmYTAzLTY3NTQtNGE0ZC1hYjU1LWZiMTdkNzc4NzRjMSJ9.eyJwYXJ0aWNpcGFudElkIjoiZTUxZDk3MTgtNGRmMS00ZWM3LTliNWUtNzRiYWIxNWVjMmQ0IiwiaWF0IjoxNjc0MDcyNDM4fQ.2HVqP5rfE38mtuwRHgPdEJ22RHxGzcnL6g64gngIqYk\"\n" +
+ " },\n" +
+ " \"guest_url\": {\n" +
+ " \"href\": \"https://meetings.vonage.com/710363620\"\n" +
+ " }\n" +
+ " },\n" +
+ " \"created_at\": \"2023-01-18T16:57:27.828Z\",\n" +
+ " \"is_available\": true,\n" +
+ " \"expire_after_use\": true,\n" +
+ " \"theme_id\": null,\n" +
+ " \"initial_join_options\": {\n" +
+ " \"microphone_state\": \"default\"\n" +
+ " },\n" +
+ " \"join_approval_level\": \"none\",\n" +
+ " \"ui_settings\": {\n" +
+ " \"language\": \"de\"\n" +
+ " },\n" +
+ " \"available_features\": {\n" +
+ " \"is_recording_available\": true,\n" +
+ " \"is_chat_available\": true,\n" +
+ " \"is_whiteboard_available\": true,\n" +
+ " \"is_locale_switcher_available\": false\n" +
+ " }\n" +
+ " }\n" +
+ " ],\n" +
+ " \"_links\": {\n" +
+ " \"first\": {\n" +
+ " \"href\": \"api-us.vonage.com/meetings/rooms?page_size=3\"\n" +
+ " },\n" +
+ " \"self\": {\n" +
+ " \"href\": \"api-us.vonage.com/meetings/rooms?page_size=3&start_id=1991085\"\n" +
+ " },\n" +
+ " \"prev\": {\n" +
+ " \"href\": \"api-us.vonage.com/meetings/rooms?page_size=3&end_id=1991084\"\n" +
+ " },\n" +
+ " \"next\": {\n" +
+ " \"href\": \"api-us.vonage.com/meetings/rooms?page_size=3&start_id=1994609\"\n" +
+ " }\n" +
+ " },\n" +
+ " \"total_items\": 5\n" +
+ "}",
+
+ SAMPLE_THEME_RESPONSE = "{\n" +
+ " \"theme_id\": \"d03e71e5-9336-49c1-8adc-12fb3fa98110\",\n" +
+ " \"theme_name\": \"Theme1\",\n" +
+ " \"domain\": \"VBC\",\n" +
+ " \"account_id\": \"94a99d02-24b2-445a-9526-2846aa5f846\",\n" +
+ " \"application_id\": \"862f8c7b-d203-4729-68a3-7eded210c9ca\",\n" +
+ " \"main_color\": \"#12f64e\",\n" +
+ " \"short_company_url\": \"https://t.co/acme\",\n" +
+ " \"brand_text\": \"Looney Tunes Ltd.\",\n" +
+ " \"brand_image_colored\": \"color-key\",\n" +
+ " \"brand_image_white\": \"white-key\",\n" +
+ " \"branded_favicon\": \"favicon-key\",\n" +
+ " \"brand_image_colored_url\": \"https://example.com/color.png\",\n" +
+ " \"brand_image_white_url\": \"https://example.com/white.png\",\n" +
+ " \"branded_favicon_url\": \"https://example.com/favicon.png\"\n" +
+ " }",
+
+ SAMPLE_RECORDING_RESPONSE = "{\n" +
+ " \"id\": \"497f6eca-6276-4993-bfeb-53cbbbba6f08\",\n" +
+ " \"session_id\": \"2_MX40NjMwODczMn5-MTU3NTgyODEwNzQ2MH5OZDJrVmdBRUNDbG5MUzNqNXgya20yQ1Z-fg\",\n" +
+ " \"started_at\": \"2019-08-24T14:15:22Z\",\n" +
+ " \"ended_at\": \"2019-11-30T00:59:06Z\",\n" +
+ " \"status\": \"started\",\n" +
+ " \"_links\": {\n" +
+ " \"url\": {\n" +
+ " \"href\": \"http://example.com/recording.mp4\"\n" +
+ " }\n" +
+ " }\n" +
+ "}";
+
+
+ public MeetingsClientTest() {
+ client = new MeetingsClient(wrapper);
+ }
+
+ static void assertEqualsSampleRoom(MeetingRoom parsed) {
+ assertNotNull(parsed);
+ assertEquals("b84cc862-0764-4887-9265-37e8a863164d", parsed.getId().toString());
+ assertEquals("Sina's room", parsed.getDisplayName());
+ assertEquals("foo=bar", parsed.getMetadata());
+ assertEquals(RoomType.LONG_TERM, parsed.getType());
+ Instant expiresAt = ZonedDateTime.of(
+ 3000, 1, 17,
+ 15, 53, 3,
+ 377_000_000, ZoneId.of("UTC")
+ ).truncatedTo(ChronoUnit.MILLIS).toInstant();
+ assertEquals(expiresAt, parsed.getExpiresAt());
+ assertEquals(Instant.parse("3000-01-17T15:53:03.377Z"), parsed.getExpiresAt());
+ RecordingOptions recordingOptions = parsed.getRecordingOptions();
+ assertNotNull(recordingOptions);
+ assertFalse(recordingOptions.getAutoRecord());
+ assertFalse(recordingOptions.getRecordOnlyOwner());
+ assertEquals("365658578", parsed.getMeetingCode());
+ RoomLinks roomLinks = parsed.getLinks();
+ assertNotNull(roomLinks);
+ assertTrue(roomLinks.getHostUrl().toString().startsWith("https://meetings.vonage.com/?room_token=365658578&participant_token=ey"));
+ assertEquals("https://meetings.vonage.com/365658578", roomLinks.getGuestUrl().toString());
+ Instant createdAt = ZonedDateTime.of(
+ 2023, 1, 17,
+ 16, 19, 13,
+ 518_000_000, ZoneId.of("UTC")
+ ).truncatedTo(ChronoUnit.MILLIS).toInstant();
+ assertEquals(createdAt, parsed.getCreatedAt());
+ assertEquals(Instant.parse("2023-01-17T16:19:13.518Z"), parsed.getCreatedAt());
+ assertTrue(parsed.getIsAvailable());
+ assertFalse(parsed.getExpireAfterUse());
+ assertNull(parsed.getThemeId());
+ InitialJoinOptions initialJoinOptions = parsed.getInitialJoinOptions();
+ assertNotNull(initialJoinOptions);
+ assertEquals(MicrophoneState.OFF, initialJoinOptions.getMicrophoneState());
+ assertEquals(JoinApprovalLevel.NONE, parsed.getJoinApprovalLevel());
+ UISettings uiSettings = parsed.getUiSettings();
+ assertNotNull(uiSettings);
+ CallbackUrls callbackUrls = parsed.getCallbackUrls();
+ assertNotNull(callbackUrls);
+ assertEquals("https://example.com/rooms", callbackUrls.getRoomsCallbackUrl().toString());
+ assertEquals("https://example.com/sessions", callbackUrls.getSessionsCallbackUrl().toString());
+ assertEquals("https://example.com/recordings", callbackUrls.getRecordingsCallbackUrl().toString());
+ assertEquals(RoomLanguage.DE, uiSettings.getLanguage());
+ AvailableFeatures availableFeatures = parsed.getAvailableFeatures();
+ assertNotNull(availableFeatures);
+ assertTrue(availableFeatures.getIsRecordingAvailable());
+ assertTrue(availableFeatures.getIsChatAvailable());
+ assertTrue(availableFeatures.getIsWhiteboardAvailable());
+ assertTrue(availableFeatures.getIsLocaleSwitcherAvailable());
+ }
+
+ void stubResponseAndAssertEqualsSampleRoom(Supplier extends MeetingRoom> call) throws Exception {
+ assertEqualsSampleRoom(stubResponseAndGet(200, SAMPLE_ROOM_RESPONSE, call));
+ }
+
+ static void assertEqualsAvailableRooms(List rooms) {
+ assertEquals(2, rooms.size());
+ assertEqualsSampleRoom(rooms.get(0));
+ MeetingRoom otherRoom = rooms.get(1);
+ assertEquals("2f63e54b-adc1-4dda-a27c-24f04c0f1233", otherRoom.getId().toString());
+ assertNull(otherRoom.getMetadata());
+ assertEquals(RoomType.INSTANT, otherRoom.getType());
+ assertEquals("710363620", otherRoom.getMeetingCode());
+ assertTrue(otherRoom.getIsAvailable());
+ assertTrue(otherRoom.getExpireAfterUse());
+ assertEquals(MicrophoneState.DEFAULT, otherRoom.getInitialJoinOptions().getMicrophoneState());
+ assertTrue(otherRoom.getAvailableFeatures().getIsChatAvailable());
+ assertFalse(otherRoom.getAvailableFeatures().getIsLocaleSwitcherAvailable());
+ assertEquals(JoinApprovalLevel.NONE, otherRoom.getJoinApprovalLevel());
+ assertEquals(RoomLanguage.DE, otherRoom.getUiSettings().getLanguage());
+ }
+
+ static void assertEqualsAvailableRooms(ListRoomsResponse parsed) {
+ assertEqualsAvailableRooms(parsed.getMeetingRooms());
+ HalLinks links = parsed.getLinks();
+ assertEquals("api-us.vonage.com/meetings/rooms?page_size=3", links.getFirstUrl().toString());
+ assertEquals("api-us.vonage.com/meetings/rooms?page_size=3&start_id=1991085", links.getSelfUrl().toString());
+ assertEquals("api-us.vonage.com/meetings/rooms?page_size=3&end_id=1991084", links.getPrevUrl().toString());
+ assertEquals("api-us.vonage.com/meetings/rooms?page_size=3&start_id=1994609", links.getNextUrl().toString());
+ }
+
+ static void assertEqualsSampleTheme(Theme parsed) {
+ assertNotNull(parsed);
+ assertEquals("d03e71e5-9336-49c1-8adc-12fb3fa98110", parsed.getThemeId().toString());
+ assertEquals("Theme1", parsed.getThemeName());
+ assertEquals(ThemeDomain.VBC, parsed.getDomain());
+ assertEquals("94a99d02-24b2-445a-9526-2846aa5f846", parsed.getAccountId());
+ assertEquals("862f8c7b-d203-4729-68a3-7eded210c9ca", parsed.getApplicationId().toString());
+ assertEquals("#12f64e", parsed.getMainColor());
+ assertEquals("https://t.co/acme", parsed.getShortCompanyUrl());
+ assertEquals("Looney Tunes Ltd.", parsed.getBrandText());
+ assertEquals("color-key", parsed.getBrandImageColored());
+ assertEquals("white-key", parsed.getBrandImageWhite());
+ assertEquals("favicon-key", parsed.getBrandedFavicon());
+ assertEquals("https://example.com/color.png", parsed.getBrandImageColoredUrl().toString());
+ assertEquals("https://example.com/white.png", parsed.getBrandImageWhiteUrl().toString());
+ assertEquals("https://example.com/favicon.png", parsed.getBrandedFaviconUrl().toString());
+ }
+
+ void stubResponseAndAssertEqualsSampleTheme(Supplier extends Theme> call) throws Exception {
+ assertEqualsSampleTheme(stubResponseAndGet(200, SAMPLE_THEME_RESPONSE, call));
+ }
+
+ static void assertEqualsSampleRecording(Recording parsed) {
+ assertNotNull(parsed);
+ assertEquals(UUID.fromString("497f6eca-6276-4993-bfeb-53cbbbba6f08"), parsed.getId());
+ assertEquals("2_MX40NjMwODczMn5-MTU3NTgyODEwNzQ2MH5OZDJrVmdBRUNDbG5MUzNqNXgya20yQ1Z-fg", parsed.getSessionId());
+ assertEquals(RecordingStatus.STARTED, parsed.getStatus());
+ assertEquals(URI.create("http://example.com/recording.mp4"), parsed.getUrl());
+
+ assertEquals(Instant.parse("2019-08-24T14:15:22Z"), parsed.getStartedAt());
+ Instant startedAt = ZonedDateTime.of(
+ 2019, 8, 24,
+ 14, 15, 22,
+ 0, ZoneId.of("UTC")
+ ).truncatedTo(ChronoUnit.MILLIS).toInstant();
+ assertEquals(startedAt, parsed.getStartedAt());
+
+ assertEquals(Instant.parse("2019-11-30T00:59:06Z"), parsed.getEndedAt());
+ Instant endedAt = ZonedDateTime.of(
+ 2019, 11, 30,
+ 0, 59, 6,
+ 0, ZoneId.of("UTC")
+ ).truncatedTo(ChronoUnit.MILLIS).toInstant();
+ assertEquals(endedAt, parsed.getEndedAt());
+ }
+
+ void stubResponseAndAssertEqualsSampleRecording(Supplier extends Recording> call) throws Exception {
+ assertEqualsSampleRecording(stubResponseAndGet(200, SAMPLE_RECORDING_RESPONSE, call));
+ }
+
+ void testPaginatedMeetingRoomsResponse(Supplier> call) throws Exception {
+ String firstJson = "{\n" +
+ " \"page_size\": 1000,\n" +
+ " \"total_items\": 1080,\n" +
+ " \"_embedded\": [{\"theme_id\":\""+RANDOM_ID+"\"},{}],\n" +
+ " \"_links\": {\n" +
+ " \"first\": {\n" +
+ " \"href\": \"https://api-eu.vonage.com/meetings/rooms?page_size=50\"\n" +
+ " },\n" +
+ " \"self\": {\n" +
+ " \"href\": \"https://api-eu.vonage.com/meetings/rooms?page_size=50&start_id=2293905\"\n" +
+ " },\n" +
+ " \"next\": {\n" +
+ " \"href\": \"https://api-eu.vonage.com/meetings/rooms?page_size=50&start_id=2293906\"\n" +
+ " },\n" +
+ " \"prev\": {\n" +
+ " \"href\": \"https://api-eu.vonage.com/meetings/rooms?page_size=50&start_id=2293904\"\n" +
+ " }\n" +
+ " }\n" +
+ "}",
+ secondJson = "{\n" +
+ " \"page_size\": 80,\n" +
+ " \"total_items\": 1080,\n" +
+ " \"_embedded\": [{},{},{},{},{},{},{\"id\":\""+ROOM_ID+"\"},{}],\n" +
+ " \"_links\": {\n" +
+ " \"next\": {\n" +
+ " \"href\": \"https://api-eu.vonage.com/meetings/rooms?page_size=50&start_id=2235997\"\n" +
+ " }\n" +
+ " }\n" +
+ "}";
+
+ HttpClient httpClient = stubHttpClient(200, firstJson, secondJson);
+ wrapper.setHttpClient(httpClient);
+ List rooms = call.get();
+ assertEquals(10, rooms.size());
+ assertEquals(RANDOM_ID, rooms.get(0).getThemeId());
+ assertEquals(ROOM_ID, rooms.get(8).getId());
+ }
+
+ void assert401ResponseException(ThrowingRunnable invocation) throws Exception {
+ int statusCode = 401;
+ MeetingsResponseException expectedResponse = MeetingsResponseException.fromJson(
+ "{\n" +
+ " \"title\": \"Missing Auth\",\n" +
+ " \"detail\": \"Auth header is required\"\n" +
+ "}"
+ );
+
+ expectedResponse.setStatusCode(statusCode);
+ String expectedJson = expectedResponse.toJson();
+ wrapper.setHttpClient(stubHttpClient(statusCode, expectedJson));
+ String failPrefix = "Expected "+expectedResponse.getClass().getSimpleName()+", but got ";
+
+ try {
+ invocation.run();
+ fail(failPrefix + "nothing.");
+ }
+ catch (MeetingsResponseException ex) {
+ assertEquals(expectedResponse, ex);
+ assertEquals(expectedJson, ex.toJson());
+ assertEquals("Missing Auth", ex.getTitle());
+ assertEquals("Auth header is required", ex.getDetail());
+ assertNull(ex.getType());
+ assertNull(ex.getInstance());
+ assertNull(ex.getName());
+ assertNotNull(ex.getMessage());
+ }
+ catch (Throwable ex) {
+ fail(failPrefix + ex);
+ }
+ }
+
+ void assert400ResponseException(ThrowingRunnable invocation) throws Exception {
+ int statusCode = 400;
+ MeetingsResponseException expectedResponse = MeetingsResponseException.fromJson(
+ "{\n" +
+ " \"message\": \"Explanation about why validation failed.\",\n" +
+ " \"name\": \"InputValidationError\",\n" +
+ " \"status\": "+statusCode+"\n" +
+ "}"
+ );
+
+ String expectedJson = expectedResponse.toJson();
+ wrapper.setHttpClient(stubHttpClient(statusCode, expectedJson));
+ String failPrefix = "Expected "+expectedResponse.getClass().getSimpleName()+", but got ";
+
+ try {
+ invocation.run();
+ fail(failPrefix + "nothing.");
+ }
+ catch (MeetingsResponseException ex) {
+ assertEquals(statusCode, ex.getStatusCode());
+ assertEquals(statusCode, expectedResponse.getStatusCode());
+ assertEquals("InputValidationError", ex.getName());
+ assertEquals("Explanation about why validation failed.", ex.getMessage());
+ assertEquals(expectedResponse.getName(), ex.getName());
+ assertEquals(expectedResponse.getMessage(), ex.getMessage());
+ assertNull(ex.getType());
+ assertNull(ex.getInstance());
+ assertNull(expectedResponse.getTitle());
+ assertEquals("Test reason", ex.getTitle());
+ assertNull(ex.getDetail());
+ }
+ catch (Throwable ex) {
+ fail(failPrefix + ex);
+ }
+ }
+
+ @Test
+ public void testListRooms() throws Exception {
+ assertEqualsAvailableRooms(stubResponseAndGet(200, LIST_ROOMS_RESPONSE, client::listRooms));
+ assert401ResponseException(client::listRooms);
+ testPaginatedMeetingRoomsResponse(client::listRooms);
+ }
+
+ @Test
+ public void testGetRoom() throws Exception {
+ stubResponseAndAssertEqualsSampleRoom(() -> client.getRoom(ROOM_ID));
+
+ stubResponseAndAssertThrows(200, SAMPLE_ROOM_RESPONSE,
+ () -> client.getRoom(null), NullPointerException.class
+ );
+ assert401ResponseException(() -> client.getRoom(ROOM_ID));
+ }
+
+ @Test
+ public void testUpdateRoom() throws Exception {
+ UpdateRoomRequest request = UpdateRoomRequest.builder().build();
+ stubResponseAndAssertEqualsSampleRoom(() -> client.updateRoom(ROOM_ID, request));
+
+ stubResponseAndAssertThrows(200, SAMPLE_ROOM_RESPONSE,
+ () -> client.updateRoom(ROOM_ID, null), NullPointerException.class
+ );
+ stubResponseAndAssertThrows(200, SAMPLE_ROOM_RESPONSE,
+ () -> client.updateRoom(null, request), NullPointerException.class
+ );
+ assert401ResponseException(() -> client.updateRoom(ROOM_ID, request));
+ }
+
+ @Test
+ public void testCreateRoom() throws Exception {
+ MeetingRoom request = MeetingRoom.builder("Sample mr").build();
+ stubResponseAndAssertEqualsSampleRoom(() -> client.createRoom(request));
+
+ stubResponseAndAssertThrows(201, SAMPLE_ROOM_RESPONSE,
+ () -> client.createRoom(null), NullPointerException.class
+ );
+ assert400ResponseException(() -> client.createRoom(request));
+ }
+
+ @Test
+ public void testSearchRoomsByTheme() throws Exception {
+ assertEqualsAvailableRooms(stubResponseAndGet(200, LIST_ROOMS_RESPONSE,
+ () -> client.searchRoomsByTheme(RANDOM_ID))
+ );
+ stubResponseAndAssertThrows(200, LIST_ROOMS_RESPONSE,
+ () -> client.searchRoomsByTheme(null), NullPointerException.class
+ );
+ assert401ResponseException(() -> client.searchRoomsByTheme(RANDOM_ID));
+ testPaginatedMeetingRoomsResponse(() -> client.searchRoomsByTheme(RANDOM_ID));
+ }
+
+ @Test
+ public void testListThemes() throws Exception {
+ String responseJson = "["+SAMPLE_THEME_RESPONSE+",{\"theme_id\":\""+RANDOM_ID+"\"}]";
+ List parsed = stubResponseAndGet(200, responseJson, client::listThemes);
+ assertEquals(2, parsed.size());
+ assertEqualsSampleTheme(parsed.get(0));
+ assertEquals(RANDOM_ID, parsed.get(1).getThemeId());
+ assert401ResponseException(client::listThemes);
+ }
+
+ @Test
+ public void testGetTheme() throws Exception {
+ stubResponseAndAssertEqualsSampleTheme(() -> client.getTheme(RANDOM_ID));
+
+ stubResponseAndAssertThrows(200, SAMPLE_THEME_RESPONSE,
+ () -> client.getTheme(null), NullPointerException.class
+ );
+ assert401ResponseException(() -> client.getTheme(RANDOM_ID));
+ }
+
+ @Test
+ public void testCreateTheme() throws Exception {
+ Theme request = Theme.builder().brandText("My Company").mainColor("#fff000").build();
+ stubResponseAndAssertEqualsSampleTheme(() -> client.createTheme(request));
+
+ stubResponseAndAssertThrows(200, SAMPLE_THEME_RESPONSE,
+ () -> client.createTheme(null), NullPointerException.class
+ );
+ assert400ResponseException(() -> client.createTheme(request));
+ }
+
+ @Test
+ public void testUpdateTheme() throws Exception {
+ Theme request = Theme.builder().build();
+ stubResponseAndAssertEqualsSampleTheme(() -> client.updateTheme(RANDOM_ID, request));
+
+ stubResponseAndAssertThrows(200, SAMPLE_THEME_RESPONSE,
+ () -> client.updateTheme(RANDOM_ID, null), NullPointerException.class
+ );
+ stubResponseAndAssertThrows(200, SAMPLE_THEME_RESPONSE,
+ () -> client.updateTheme(null, request), NullPointerException.class
+ );
+ assert400ResponseException(() -> client.updateTheme(RANDOM_ID, request));
+ }
+
+ @Test
+ public void testDeleteTheme() throws Exception {
+ stubResponseAndRun(204, () -> client.deleteTheme(RANDOM_ID, false));
+
+ stubResponseAndAssertThrows(204,
+ () -> client.deleteTheme(null, true), NullPointerException.class
+ );
+ assert401ResponseException(() -> client.deleteTheme(RANDOM_ID, true));
+ }
+
+ @Test
+ public void testListRecordings() throws Exception {
+ String sessionId = "session_id123";
+ String responseJson = "{\"_embedded\":{\"recordings\":[" + SAMPLE_RECORDING_RESPONSE + ",{}]}}";
+ stubResponse(200, responseJson);
+ List recordings = client.listRecordings(sessionId);
+ assertEquals(2, recordings.size());
+ assertEqualsSampleRecording(recordings.get(0));
+ assertNotNull(recordings.get(1));
+
+ responseJson = "{\"_embedded\":{}}";
+ stubResponse(200, responseJson);
+ recordings = client.listRecordings(sessionId);
+ assertNotNull(recordings);
+ assertEquals(0, recordings.size());
+
+ responseJson = "{\"_embedded\":{\"recordings\":[]}}";
+ stubResponse(200, responseJson);
+ recordings = client.listRecordings(sessionId);
+ assertNotNull(recordings);
+ assertEquals(0, recordings.size());
+
+ stubResponseAndAssertThrows(200,
+ () -> client.listRecordings(null), IllegalArgumentException.class
+ );
+ assert401ResponseException(() -> client.listRecordings(sessionId));
+ }
+
+ @Test
+ public void testGetRecording() throws Exception {
+ stubResponseAndAssertEqualsSampleRecording(() -> client.getRecording(RANDOM_ID));
+
+ stubResponseAndAssertThrows(200,
+ () -> client.getRecording(null), NullPointerException.class
+ );
+ assert401ResponseException(() -> client.getRecording(RANDOM_ID));
+ }
+
+ @Test
+ public void testDeleteRecording() throws Exception {
+ stubResponseAndRun(204, () -> client.deleteRecording(RANDOM_ID));
+
+ stubResponseAndAssertThrows(204,
+ () -> client.deleteRecording(null), NullPointerException.class
+ );
+ assert401ResponseException(() -> client.deleteRecording(RANDOM_ID));
+ }
+
+ @Test
+ public void testListDialNumbers() throws Exception {
+ String responseJson = "[\n" +
+ "{\"number\":\"17329672755\",\"display_name\":\"United States\",\"locale\":\"en_US\"},\n" +
+ "{\"number\":\"48123964788\",\"display_name\":\"Poland\",\"locale\":\"pl_PL\"},\n" +
+ "{\"number\":\"827047844377\",\"display_name\":\"South Korea\",\"locale\":\"ko_KR\"}\n" +
+ "]";
+ List parsed = stubResponseAndGet(200, responseJson, client::listDialNumbers);
+ assertEquals(3, parsed.size());
+ assertEquals("17329672755", parsed.get(0).getNumber());
+ assertEquals("United States", parsed.get(0).getDisplayName());
+ assertEquals(Locale.US, parsed.get(0).getLocale());
+ assertEquals("48123964788", parsed.get(1).getNumber());
+ assertEquals("Poland", parsed.get(1).getDisplayName());
+ assertEquals(Locale.forLanguageTag("pl-PL"), parsed.get(1).getLocale());
+ assertEquals("827047844377", parsed.get(2).getNumber());
+ assertEquals("South Korea", parsed.get(2).getDisplayName());
+ assertEquals(Locale.KOREA, parsed.get(2).getLocale());
+ assert401ResponseException(client::listDialNumbers);
+ }
+
+ @Test
+ public void testUpdateApplication() throws Exception {
+ UpdateApplicationRequest request = UpdateApplicationRequest.builder().defaultThemeId(RANDOM_ID).build();
+ String accId = "account-id_123";
+ UUID appId = UUID.randomUUID();
+ String responseJson = "{\n" +
+ " \"application_id\": \""+appId+"\",\n" +
+ " \"account_id\": \""+accId+"\",\n" +
+ " \"default_theme_id\": \""+RANDOM_ID+"\"\n" +
+ "}";
+ Application parsed = stubResponseAndGet(200, responseJson, () -> client.updateApplication(request));
+ assertEquals(appId, parsed.getApplicationId());
+ assertEquals(accId, parsed.getAccountId());
+ assertEquals(RANDOM_ID, parsed.getDefaultThemeId());
+
+ stubResponseAndAssertThrows(200, responseJson,
+ () -> client.updateApplication(null), NullPointerException.class
+ );
+ assert400ResponseException(() -> client.updateApplication(request));
+ }
+
+ @Test
+ public void testFinalizeLogos() throws Exception {
+ final String logoKey = "greyscale-logo-key0";
+ stubResponseAndRun(200,() -> client.finalizeLogos(RANDOM_ID, Arrays.asList("key1", "l-k-2", "k3")));
+ stubResponseAndRun(200, () -> client.finalizeLogos(RANDOM_ID, Collections.singletonList("a")));
+
+ stubResponseAndAssertThrows(200,
+ () -> client.finalizeLogos(null, Arrays.asList(logoKey)), NullPointerException.class
+ );
+ stubResponseAndAssertThrows(200,
+ () -> client.finalizeLogos(RANDOM_ID, null), IllegalArgumentException.class
+ );
+ stubResponseAndAssertThrows(200,
+ () -> client.finalizeLogos(RANDOM_ID, Collections.emptyList()), IllegalArgumentException.class
+ );
+
+ MeetingsResponseException expectedResponse = MeetingsResponseException.fromJson(
+ "{\n" +
+ " \"status\": 400,\n" +
+ " \"name\": \"BadRequestError\",\n" +
+ " \"message\": \"could not finalize logos\",\n" +
+ " \"errors\": [\n" +
+ " {\n" +
+ " \"logoKey\": \""+logoKey+"\",\n" +
+ " \"code\": \"invalid_logo_properties\",\n" +
+ " \"invalidProperty\": \"exceeds_size\"\n" +
+ " }\n" +
+ " ]\n" +
+ "}"
+ );
+
+ wrapper.setHttpClient(stubHttpClient(400, expectedResponse.toJson()));
+
+ try {
+ client.finalizeLogos(RANDOM_ID, Collections.singletonList(logoKey));
+ fail("Expected "+expectedResponse.getClass().getSimpleName());
+ }
+ catch (MeetingsResponseException ex) {
+ assertEquals(400, ex.getStatusCode());
+ assertEquals(ex.getStatusCode(), expectedResponse.getStatusCode());
+ assertEquals("BadRequestError", ex.getName());
+ assertEquals("could not finalize logos", ex.getMessage());
+ assertEquals(expectedResponse.getName(), ex.getName());
+ assertEquals(expectedResponse.getMessage(), ex.getMessage());
+ assertNull(ex.getType());
+ assertNull(ex.getInstance());
+ assertNull(expectedResponse.getTitle());
+ assertEquals("Test reason", ex.getTitle());
+ assertNull(ex.getDetail());
+
+ List> errors = ex.getErrors();
+ assertNotNull(errors);
+ assertEquals(1, errors.size());
+ @SuppressWarnings("unchecked")
+ Map firstError = (Map) errors.get(0);
+ assertNotNull(firstError);
+ assertEquals(3, firstError.size());
+ assertEquals(firstError.get("logoKey"), logoKey);
+ assertEquals(firstError.get("code"), "invalid_logo_properties");
+ assertEquals(firstError.get("invalidProperty"), "exceeds_size");
+ assertEquals(expectedResponse.getErrors(), errors);
+ }
+ }
+
+ @Test
+ public void testListLogoUploadUrls() throws Exception {
+ String responseJson = "[\n" +
+ "{}, {\n" +
+ " \"url\": \"https://storage-url.com\",\n" +
+ " \"fields\": {\n" +
+ " \"Content-Type\": \"image/png\",\n" +
+ " \"key\": \"auto-expiring-temp/logos/white/ca63a155-d\",\n" +
+ " \"logoType\": \"white\",\n" +
+ " \"bucket\": \"s3\",\n" +
+ " \"X-Amz-Algorithm\": \"stringA\",\n" +
+ " \"X-Amz-Credential\": \"stringC\",\n" +
+ " \"X-Amz-Date\": \"stringD\",\n" +
+ " \"X-Amz-Security-Token\": \"stringT\",\n" +
+ " \"Policy\": \"stringP\",\n" +
+ " \"X-Amz-Signature\": \"stringS\"\n" +
+ " }\n" +
+ " }, {\"url\": \"http://example.com\"}\n" +
+ "]";
+ stubResponse(200, responseJson);
+ List parsed = client.listLogoUploadUrls();
+ assertEquals(3, parsed.size());
+ assertNotNull(parsed.get(0));
+ assertEquals("http://example.com", parsed.get(2).getUrl().toString());
+ LogoUploadsUrlResponse sample = parsed.get(1);
+ LogoUploadsUrlResponse.Fields fields = sample.getFields();
+ assertNotNull(fields);
+ assertEquals("https://storage-url.com", sample.getUrl().toString());
+ assertEquals("image/png", fields.getContentType().toString());
+ assertEquals("auto-expiring-temp/logos/white/ca63a155-d", fields.getKey());
+ assertEquals(LogoType.WHITE, fields.getLogoType());
+ assertEquals("s3", fields.getBucket());
+ assertEquals("stringA", fields.getAmzAlgorithm());
+ assertEquals("stringC", fields.getAmzCredential());
+ assertEquals("stringD", fields.getAmzDate());
+ assertEquals("stringT", fields.getAmzSecurityToken());
+ assertEquals("stringP", fields.getPolicy());
+ assertEquals("stringS", fields.getAmzSignature());
+ assert401ResponseException(client::listLogoUploadUrls);
+ }
+
+ @Test
+ public void testGetUploadDetailsForLogoType() throws Exception {
+ String whiteKey = "blanc", colourKey = "colour", faviconKey = "favourite";
+ String responseJson = "[\n" +
+ "{\"fields\":{\"key\": \""+whiteKey+"\",\"logoType\": \"white\"}},\n" +
+ "{\"fields\":{\"key\": \""+colourKey+"\",\"logoType\": \"colored\"}},\n" +
+ "{\"fields\":{\"key\": \""+faviconKey+"\",\"logoType\": \"favicon\"}}]";
+
+ stubResponse(200, responseJson);
+ LogoUploadsUrlResponse colour = client.getUploadDetailsForLogoType(LogoType.COLORED);
+ assertEquals(LogoType.COLORED, colour.getFields().getLogoType());
+ assertEquals(colourKey, colour.getFields().getKey());
+
+ stubResponse(200, responseJson);
+ LogoUploadsUrlResponse favicon = client.getUploadDetailsForLogoType(LogoType.FAVICON);
+ assertEquals(LogoType.FAVICON, favicon.getFields().getLogoType());
+ assertEquals(faviconKey, favicon.getFields().getKey());
+
+ stubResponse(200, responseJson);
+ LogoUploadsUrlResponse white = client.getUploadDetailsForLogoType(LogoType.WHITE);
+ assertEquals(LogoType.WHITE, white.getFields().getLogoType());
+ assertEquals(whiteKey, white.getFields().getKey());
+ }
+
+ @Test
+ public void testUploadLogo() throws Exception {
+ LogoUploadsUrlResponse details = new ObjectMapper().readerFor(LogoUploadsUrlResponse.class).readValue((
+ "{\"url\":\"https://s3.amazonaws.com/roomservice-whitelabel-logos-prod\",\"fields\":{" +
+ "\"Content-Type\":\"image/png\",\"key\":\"auto-expiring-temp/logos/colored/REDACTED\"," +
+ "\"logoType\":\"colored\",\"bucket\":\"roomservice-whitelabel-logos-prod\"," +
+ "\"X-Amz-Algorithm\":\"AWS4-HMAC-SHA256\",\"X-Amz-Credential\":" +
+ "\"REDACTED/20230130/us-east-1/s3/aws4_request\",\"X-Amz-Date\":\"20230130T113037Z\"," +
+ "\"X-Amz-Security-Token\":\"REDACTED\",\"Policy\":\"REDACTED\",\"X-Amz-Signature\":\"REDACTED\"}}"
+ ));
+ Path image = Paths.get("/path/to/logo.png");
+
+ client.httpClient = stubHttpClient(204);
+ client.uploadLogo(image, details);
+
+ client.httpClient = stubHttpClient(400);
+ assertThrows(MeetingsResponseException.class, () -> client.uploadLogo(image, details));
+
+ client.httpClient = stubHttpClient(200);
+ when(client.httpClient.execute(any())).thenThrow(IOException.class);
+ assertThrows(VonageUnexpectedException.class, () -> client.uploadLogo(image, details));
+ }
+
+ @Test
+ public void testUpdateThemeLogo() throws Exception {
+ String responseJson = "[{\"url\":\"" +
+ "https://s3.amazonaws.com/roomservice-whitelabel-logos-prod\",\"fields\":{" +
+ "\"Content-Type\":\"image/png\",\"key\":\"auto-expiring-temp/logos/favicon/REDACTED\"," +
+ "\"logoType\":\"favicon\",\"bucket\":\"roomservice-whitelabel-logos-prod\"," +
+ "\"X-Amz-Algorithm\":\"AWS4-HMAC-SHA256\",\"X-Amz-Credential\":" +
+ "\"REDACTED/20230130/us-east-1/s3/aws4_request\",\"X-Amz-Date\":\"20230130T113037Z\"," +
+ "\"X-Amz-Security-Token\":\"REDACTED\",\"Policy\":\"REDACTED\",\"X-Amz-Signature\":\"REDACTED\"}}]";
+
+ Path image = Paths.get("/path/to/logo.png");
+ stubResponse(200, responseJson);
+ client.httpClient = stubHttpClient(204);
+ client.updateThemeLogo(RANDOM_ID, LogoType.FAVICON, image);
+ }
+}
diff --git a/src/test/java/com/vonage/client/meetings/MeetingsEventCallbackTest.java b/src/test/java/com/vonage/client/meetings/MeetingsEventCallbackTest.java
new file mode 100644
index 000000000..240833107
--- /dev/null
+++ b/src/test/java/com/vonage/client/meetings/MeetingsEventCallbackTest.java
@@ -0,0 +1,293 @@
+/*
+ * Copyright 2023 Vonage
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.vonage.client.meetings;
+
+import com.vonage.client.VonageResponseParseException;
+import static junit.framework.TestCase.*;
+import org.junit.Test;
+import java.net.URI;
+import java.time.Instant;
+import java.util.UUID;
+
+public class MeetingsEventCallbackTest {
+ static final UUID
+ ROOM_ID = UUID.randomUUID(),
+ RECORDING_ID = UUID.randomUUID(),
+ PARTICIPANT_ID = UUID.randomUUID();
+ static final String
+ PARTICIPANT_NAME = "John Smith",
+ PARTICIPANT_TYPE = "Guest",
+ SESSION_ID = "2_MX40NjMwODczMn5-MTU3NTgyODEwNzQ2MH5OZDJrVmdBRUNDbG5MUzNqNX20yQ1Z-fg";
+ static final Instant
+ CREATED_AT = Instant.parse("2023-06-06T06:45:07.135Z"),
+ EXPIRES_AT = Instant.parse("2023-06-06T06:55:07.134Z"),
+ STARTED_AT = Instant.parse("2023-06-06T07:15:13.974Z"),
+ ENDED_AT = Instant.now();
+ static final URI RECORDING_URL = URI.create("https://prod-meetings-recordings.s3.amazonaws.com/123/UUID/archive.mp4");
+ static final Boolean IS_HOST = true;
+ static final Integer DURATION = 3598;
+ static final RoomType ROOM_TYPE = RoomType.INSTANT;
+
+ @Test
+ public void testRoomExpired() {
+ MeetingsEventCallback event = MeetingsEventCallback.fromJson(
+ "{\n" +
+ " \"event\": \"room:expired\",\n" +
+ " \"room_id\": \""+ROOM_ID+"\",\n" +
+ " \"room_type\": \""+ROOM_TYPE+"\",\n" +
+ " \"expires_at\": \""+EXPIRES_AT+"\",\n" +
+ " \"created_at\": \""+CREATED_AT+"\"\n" +
+ "}"
+ );
+
+ assertNotNull(event);
+ assertEquals(EventType.ROOM_EXPIRED, event.getEvent());
+ assertEquals(ROOM_ID, event.getRoomId());
+ assertEquals(ROOM_TYPE, event.getRoomType());
+ assertEquals(CREATED_AT, event.getCreatedAt());
+ assertNull(event.getStartedAt());
+ assertEquals(EXPIRES_AT, event.getExpiresAt());
+ assertNull(event.getEndedAt());
+ assertNull(event.getSessionId());
+ assertNull(event.getParticipantId());
+ assertNull(event.getParticipantName());
+ assertNull(event.getParticipantType());
+ assertNull(event.getIsHost());
+ assertNull(event.getRecordingId());
+ assertNull(event.getRecordingUrl());
+ assertNull(event.getDuration());
+ }
+
+ @Test
+ public void testSessionStarted() {
+ MeetingsEventCallback event = MeetingsEventCallback.fromJson(
+ "{\n" +
+ " \"event\": \"session:started\",\n" +
+ " \"session_id\": \""+SESSION_ID+"\",\n" +
+ " \"room_id\": \""+ROOM_ID+"\",\n" +
+ " \"started_at\": \""+STARTED_AT+"\"\n" +
+ "}"
+ );
+
+ assertNotNull(event);
+ assertEquals(EventType.SESSION_STARTED, event.getEvent());
+ assertEquals(ROOM_ID, event.getRoomId());
+ assertNull(event.getRoomType());
+ assertNull(event.getCreatedAt());
+ assertEquals(STARTED_AT, event.getStartedAt());
+ assertNull(event.getExpiresAt());
+ assertNull(event.getEndedAt());
+ assertEquals(SESSION_ID, event.getSessionId());
+ assertNull(event.getParticipantId());
+ assertNull(event.getParticipantName());
+ assertNull(event.getParticipantType());
+ assertNull(event.getIsHost());
+ assertNull(event.getRecordingId());
+ assertNull(event.getRecordingUrl());
+ assertNull(event.getDuration());
+ }
+
+ @Test
+ public void testSessionEnded() {
+ MeetingsEventCallback event = MeetingsEventCallback.fromJson(
+ "{\n" +
+ " \"event\": \"session:ended\",\n" +
+ " \"session_id\": \""+SESSION_ID+"\",\n" +
+ " \"room_id\": \""+ROOM_ID+"\",\n" +
+ " \"started_at\": \""+STARTED_AT+"\",\n" +
+ " \"ended_at\": \""+ENDED_AT+"\"\n" +
+ "}"
+ );
+
+ assertNotNull(event);
+ assertEquals(EventType.SESSION_ENDED, event.getEvent());
+ assertEquals(ROOM_ID, event.getRoomId());
+ assertNull(event.getRoomType());
+ assertNull(event.getCreatedAt());
+ assertEquals(STARTED_AT, event.getStartedAt());
+ assertNull(event.getExpiresAt());
+ assertEquals(ENDED_AT, event.getEndedAt());
+ assertEquals(SESSION_ID, event.getSessionId());
+ assertNull(event.getParticipantId());
+ assertNull(event.getParticipantName());
+ assertNull(event.getParticipantType());
+ assertNull(event.getIsHost());
+ assertNull(event.getRecordingId());
+ assertNull(event.getRecordingUrl());
+ assertNull(event.getDuration());
+ }
+
+ @Test
+ public void testRecordingStarted() {
+ MeetingsEventCallback event = MeetingsEventCallback.fromJson(
+ "{\n" +
+ " \"event\": \"recording:started\",\n" +
+ " \"recording_id\": \""+RECORDING_ID+"\",\n" +
+ " \"session_id\": \""+SESSION_ID+"\"\n" +
+ "}"
+ );
+
+ assertNotNull(event);
+ assertEquals(EventType.RECORDING_STARTED, event.getEvent());
+ assertNull(event.getRoomId());
+ assertNull(event.getRoomType());
+ assertNull(event.getCreatedAt());
+ assertNull(event.getStartedAt());
+ assertNull(event.getExpiresAt());
+ assertNull(event.getEndedAt());
+ assertEquals(SESSION_ID, event.getSessionId());
+ assertNull(event.getParticipantId());
+ assertNull(event.getParticipantName());
+ assertNull(event.getParticipantType());
+ assertNull(event.getIsHost());
+ assertEquals(RECORDING_ID, event.getRecordingId());
+ assertNull(event.getRecordingUrl());
+ assertNull(event.getDuration());
+ }
+
+ @Test
+ public void testRecordingEnded() {
+ MeetingsEventCallback event = MeetingsEventCallback.fromJson(
+ "{\n" +
+ " \"event\": \"recording:ended\",\n" +
+ " \"recording_id\": \""+RECORDING_ID+"\",\n" +
+ " \"session_id\": \""+SESSION_ID+"\",\n" +
+ " \"started_at\": \""+STARTED_AT+"\",\n" +
+ " \"ended_at\": \""+ENDED_AT+"\",\n" +
+ " \"duration\": "+DURATION+"\n" +
+ "}"
+ );
+
+ assertNotNull(event);
+ assertEquals(EventType.RECORDING_ENDED, event.getEvent());
+ assertNull(event.getRoomId());
+ assertNull(event.getRoomType());
+ assertNull(event.getCreatedAt());
+ assertEquals(STARTED_AT, event.getStartedAt());
+ assertNull(event.getExpiresAt());
+ assertEquals(ENDED_AT, event.getEndedAt());
+ assertEquals(SESSION_ID, event.getSessionId());
+ assertNull(event.getParticipantId());
+ assertNull(event.getParticipantName());
+ assertNull(event.getParticipantType());
+ assertNull(event.getIsHost());
+ assertEquals(RECORDING_ID, event.getRecordingId());
+ assertNull(event.getRecordingUrl());
+ assertEquals(DURATION, event.getDuration());
+ }
+
+ @Test
+ public void testRecordingUploaded() {
+ MeetingsEventCallback event = MeetingsEventCallback.fromJson(
+ "{\n" +
+ " \"event\": \"recording:uploaded\",\n" +
+ " \"recording_id\": \""+RECORDING_ID+"\",\n" +
+ " \"session_id\": \""+SESSION_ID+"\",\n" +
+ " \"room_id\": \""+ROOM_ID+"\",\n" +
+ " \"started_at\": \""+STARTED_AT+"\",\n" +
+ " \"ended_at\": \""+ENDED_AT+"\",\n" +
+ " \"duration\": \""+DURATION+"\",\n" +
+ " \"url\": \""+ RECORDING_URL +"\"\n" +
+ "}"
+ );
+
+ assertNotNull(event);
+ assertEquals(EventType.RECORDING_UPLOADED, event.getEvent());
+ assertEquals(ROOM_ID, event.getRoomId());
+ assertNull(event.getRoomType());
+ assertNull(event.getCreatedAt());
+ assertEquals(STARTED_AT, event.getStartedAt());
+ assertNull(event.getExpiresAt());
+ assertEquals(ENDED_AT, event.getEndedAt());
+ assertEquals(SESSION_ID, event.getSessionId());
+ assertNull(event.getParticipantId());
+ assertNull(event.getParticipantName());
+ assertNull(event.getParticipantType());
+ assertNull(event.getIsHost());
+ assertEquals(RECORDING_ID, event.getRecordingId());
+ assertEquals(RECORDING_URL, event.getRecordingUrl());
+ assertEquals(DURATION, event.getDuration());
+ }
+
+ @Test
+ public void testParticipantJoined() {
+ MeetingsEventCallback event = MeetingsEventCallback.fromJson(
+ "{\n" +
+ " \"event\": \"session:participant:joined\",\n" +
+ " \"participant_id\": \""+PARTICIPANT_ID+"\",\n" +
+ " \"session_id\": \""+SESSION_ID+"\",\n" +
+ " \"room_id\": \""+ROOM_ID+"\",\n" +
+ " \"name\": \""+PARTICIPANT_NAME+"\",\n" +
+ " \"type\": \""+PARTICIPANT_TYPE+"\",\n" +
+ " \"is_host\": "+IS_HOST+"\n" +
+ "}"
+ );
+
+ assertNotNull(event);
+ assertEquals(EventType.SESSION_PARTICIPANT_JOINED, event.getEvent());
+ assertEquals(ROOM_ID, event.getRoomId());
+ assertNull(event.getRoomType());
+ assertNull(event.getCreatedAt());
+ assertNull(event.getStartedAt());
+ assertNull(event.getExpiresAt());
+ assertNull(event.getEndedAt());
+ assertEquals(SESSION_ID, event.getSessionId());
+ assertEquals(PARTICIPANT_ID, event.getParticipantId());
+ assertEquals(PARTICIPANT_NAME, event.getParticipantName());
+ assertEquals(PARTICIPANT_TYPE, event.getParticipantType());
+ assertEquals(IS_HOST, event.getIsHost());
+ assertNull(event.getRecordingId());
+ assertNull(event.getRecordingUrl());
+ assertNull(event.getDuration());
+ }
+
+ @Test
+ public void testParticipantLeft() {
+ MeetingsEventCallback event = MeetingsEventCallback.fromJson(
+ "{\n" +
+ " \"event\": \"session:participant:left\",\n" +
+ " \"participant_id\": \""+PARTICIPANT_ID+"\",\n" +
+ " \"session_id\": \""+SESSION_ID+"\",\n" +
+ " \"room_id\": \""+ROOM_ID+"\",\n" +
+ " \"name\": \""+PARTICIPANT_NAME+"\",\n" +
+ " \"type\": \""+PARTICIPANT_TYPE+"\",\n" +
+ " \"is_host\": "+IS_HOST+"\n" +
+ "}"
+ );
+
+ assertNotNull(event);
+ assertEquals(EventType.SESSION_PARTICIPANT_LEFT, event.getEvent());
+ assertEquals(ROOM_ID, event.getRoomId());
+ assertNull(event.getRoomType());
+ assertNull(event.getCreatedAt());
+ assertNull(event.getStartedAt());
+ assertNull(event.getExpiresAt());
+ assertNull(event.getEndedAt());
+ assertEquals(SESSION_ID, event.getSessionId());
+ assertEquals(PARTICIPANT_ID, event.getParticipantId());
+ assertEquals(PARTICIPANT_NAME, event.getParticipantName());
+ assertEquals(PARTICIPANT_TYPE, event.getParticipantType());
+ assertEquals(IS_HOST, event.getIsHost());
+ assertNull(event.getRecordingId());
+ assertNull(event.getRecordingUrl());
+ assertNull(event.getDuration());
+ }
+
+ @Test(expected = VonageResponseParseException.class)
+ public void testFromJsonInvalid() {
+ MeetingsEventCallback.fromJson("{malformed]");
+ }
+}
diff --git a/src/test/java/com/vonage/client/meetings/SearchThemeRoomsEndpointTest.java b/src/test/java/com/vonage/client/meetings/SearchThemeRoomsEndpointTest.java
new file mode 100644
index 000000000..aa289488f
--- /dev/null
+++ b/src/test/java/com/vonage/client/meetings/SearchThemeRoomsEndpointTest.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2023 Vonage
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.vonage.client.meetings;
+
+import com.vonage.client.HttpConfig;
+import com.vonage.client.HttpWrapper;
+import com.vonage.client.TestUtils;
+import com.vonage.client.auth.JWTAuthMethod;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.HttpResponseException;
+import org.apache.http.client.methods.RequestBuilder;
+import org.apache.http.entity.ContentType;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import org.junit.Before;
+import org.junit.Test;
+import java.util.Map;
+import java.util.UUID;
+
+public class SearchThemeRoomsEndpointTest {
+ private SearchThemeRoomsEndpoint endpoint;
+
+ @Before
+ public void setUp() {
+ endpoint = new SearchThemeRoomsEndpoint(new HttpWrapper(new JWTAuthMethod("app-id", new byte[0])));
+ }
+
+ @Test
+ public void testMakeRequestAllParameters() throws Exception {
+ ListRoomsRequest request = new ListRoomsRequest(21, 33, 50, UUID.randomUUID());
+ RequestBuilder builder = endpoint.makeRequest(request);
+ assertEquals("GET", builder.getMethod());
+ String expectedUri = "https://api-eu.vonage.com/meetings/themes/"+request.themeId+
+ "/rooms?start_id="+request.startId+"&end_id="+request.endId+"&page_size="+request.pageSize;
+ assertEquals(expectedUri, builder.build().getURI().toString());
+ Map params = TestUtils.makeParameterMap(builder.getParameters());
+ assertEquals(3, params.size());
+
+ assertEquals(ContentType.APPLICATION_JSON.getMimeType(), builder.getFirstHeader("Accept").getValue());
+ String expectedResponse = "{\n" +
+ "\"page_size\":"+request.pageSize+",\"_embedded\":[{},{},{}," +
+ "{\"type\":\"instant\"},{\"nonsense\":0}],\"_links\":{\n" +
+ "\"self\":{\"href\":\"https://vonage.com\"},\"prev\":{},\"next\":{\"href\":null,\"no\":1}}}";
+ HttpResponse mockResponse = TestUtils.makeJsonHttpResponse(200, expectedResponse);
+ ListRoomsResponse parsed = endpoint.parseResponse(mockResponse);
+ assertEquals(request.pageSize, parsed.getPageSize());
+ assertEquals(5, parsed.getMeetingRooms().size());
+ assertEquals(RoomType.INSTANT, parsed.getMeetingRooms().get(3).getType());
+ assertNull(parsed.getLinks().getNextUrl());
+ assertNull(parsed.getLinks().getPrevUrl());
+ assertNull(parsed.getLinks().getFirstUrl());
+ assertEquals("https://vonage.com", parsed.getLinks().getSelfUrl().toString());
+ }
+
+ @Test
+ public void testCustomUri() throws Exception {
+ String baseUri = "http://example.com";
+ HttpWrapper wrapper = new HttpWrapper(HttpConfig.builder().baseUri(baseUri).build());
+ endpoint = new SearchThemeRoomsEndpoint(wrapper);
+ ListRoomsRequest request = new ListRoomsRequest(1, null, null, UUID.randomUUID());
+ String expectedUri = baseUri + "/meetings/themes/"+request.themeId+"/rooms?start_id="+request.startId;
+ RequestBuilder builder = endpoint.makeRequest(request);
+ assertEquals(expectedUri, builder.build().getURI().toString());
+ Map params = TestUtils.makeParameterMap(builder.getParameters());
+ assertEquals(1, params.size());
+ assertEquals(ContentType.APPLICATION_JSON.getMimeType(), builder.getFirstHeader("Accept").getValue());
+ assertEquals("GET", builder.getMethod());
+ }
+
+ @Test(expected = MeetingsResponseException.class)
+ public void testUnsuccessfulResponse() throws Exception {
+ endpoint.parseResponse(TestUtils.makeJsonHttpResponse(400, "{}"));
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/vonage/client/meetings/ThemeTest.java b/src/test/java/com/vonage/client/meetings/ThemeTest.java
new file mode 100644
index 000000000..7dc67ee24
--- /dev/null
+++ b/src/test/java/com/vonage/client/meetings/ThemeTest.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright 2023 Vonage
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.vonage.client.meetings;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.vonage.client.VonageResponseParseException;
+import com.vonage.client.VonageUnexpectedException;
+import static org.junit.Assert.*;
+import org.junit.Test;
+import java.net.URI;
+import java.util.UUID;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+public class ThemeTest {
+
+ @Test
+ public void testSerializeAndParseCommonParameters() {
+ String themeName = "Test theme 1";
+ String mainColor = "#1e1e1f";
+ String shortCompanyUrl = "t.co/VonageDev";
+ String brandText = "Nexmo";
+ Theme request = Theme.builder()
+ .themeName(themeName)
+ .mainColor(mainColor)
+ .shortCompanyUrl(shortCompanyUrl)
+ .brandText(brandText)
+ .build();
+
+ String json = request.toJson();
+ assertTrue(json.contains("\"theme_name\":\""+themeName+"\""));
+ assertTrue(json.contains("\"main_color\":\""+mainColor+"\""));
+ assertTrue(json.contains("\"short_company_url\":\""+shortCompanyUrl+"\""));
+ assertTrue(json.contains("\"brand_text\":\""+brandText+"\""));
+
+ Theme response = Theme.fromJson(json);
+ assertEquals(request.getThemeName(), response.getThemeName());
+ assertEquals(request.getMainColor(), response.getMainColor());
+ assertEquals(request.getShortCompanyUrl(), response.getShortCompanyUrl());
+ assertEquals(request.getBrandText(), response.getBrandText());
+ }
+
+ @Test
+ public void testFromJsonAllFields() {
+ UUID themeId = UUID.randomUUID();
+ String themeName = "Another name";
+ ThemeDomain domain = ThemeDomain.VCP;
+ String accountId = "account identifier";
+ UUID applicationId = UUID.randomUUID();
+ String mainColor = "#fff000";
+ String shortCompanyUrl = "www.example.com";
+ String brandText = "Test brand";
+ String brandImageColored = "color_key";
+ String brandImageWhite = "white_key";
+ String brandedFavicon = "favicon_key";
+ URI brandImageColoredUrl = URI.create("https://example.com/colour.jpg");
+ URI brandImageWhiteUrl = URI.create("https://example.com/white.jpg");
+ URI brandedFaviconUrl = URI.create("https://example.com/favicon.jpg");
+
+ Theme response = Theme.fromJson("{\n" +
+ "\"theme_id\":\""+themeId+"\",\n" +
+ "\"theme_name\":\""+themeName+"\",\n" +
+ "\"domain\":\""+domain+"\",\n" +
+ "\"account_id\":\""+accountId+"\",\n" +
+ "\"application_id\":\""+applicationId+"\",\n" +
+ "\"main_color\":\""+mainColor+"\",\n" +
+ "\"short_company_url\":\""+shortCompanyUrl+"\",\n" +
+ "\"brand_text\":\""+brandText+"\",\n" +
+ "\"brand_image_colored\":\""+brandImageColored+"\",\n" +
+ "\"brand_image_white\":\""+brandImageWhite+"\",\n" +
+ "\"branded_favicon\":\""+brandedFavicon+"\",\n" +
+ "\"brand_image_colored_url\":\""+brandImageColoredUrl+"\",\n" +
+ "\"brand_image_white_url\":\""+brandImageWhiteUrl+"\",\n" +
+ "\"branded_favicon_url\":\""+brandedFaviconUrl+"\"\n" +
+ "}");
+
+ assertEquals(themeId, response.getThemeId());
+ assertEquals(themeName, response.getThemeName());
+ assertEquals(domain, response.getDomain());
+ assertEquals(accountId, response.getAccountId());
+ assertEquals(applicationId, response.getApplicationId());
+ assertEquals(mainColor, response.getMainColor());
+ assertEquals(shortCompanyUrl, response.getShortCompanyUrl());
+ assertEquals(brandText, response.getBrandText());
+ assertEquals(brandImageColored, response.getBrandImageColored());
+ assertEquals(brandImageWhite, response.getBrandImageWhite());
+ assertEquals(brandedFavicon, response.getBrandedFavicon());
+ assertEquals(brandImageColoredUrl, response.getBrandImageColoredUrl());
+ assertEquals(brandImageWhiteUrl, response.getBrandImageWhiteUrl());
+ assertEquals(brandedFaviconUrl, response.getBrandedFaviconUrl());
+ }
+
+ @Test
+ public void testUpdateFromJson() {
+ Theme theme = Theme.builder().brandText("Initial text").mainColor("#a1b2c3").build();
+ assertEquals("Initial text", theme.getBrandText());
+ assertNull(theme.getThemeName());
+ assertNull(theme.getThemeId());
+
+ theme.updateFromJson("{\"theme_id\":\""+UUID.randomUUID()+"\"," +
+ "\"brand_text\":\"Nexmo\",\"theme_name\":\"test Theme\"}"
+ );
+ assertNotNull(theme.getThemeId());
+ assertEquals("#a1b2c3", theme.getMainColor());
+ assertEquals("Nexmo", theme.getBrandText());
+ assertNotNull(theme.getThemeName());
+
+ assertThrows(VonageResponseParseException.class, () -> theme.updateFromJson("{malformed]"));
+ }
+
+ @Test(expected = VonageUnexpectedException.class)
+ public void testFromJsonInvalid() {
+ Theme.fromJson("{malformed]");
+ }
+
+ @Test
+ public void testFromJsonEmpty() {
+ Theme response = Theme.fromJson("{}");
+ assertNull(response.getThemeId());
+ assertNull(response.getThemeName());
+ assertNull(response.getDomain());
+ assertNull(response.getAccountId());
+ assertNull(response.getApplicationId());
+ assertNull(response.getMainColor());
+ assertNull(response.getShortCompanyUrl());
+ assertNull(response.getBrandText());
+ assertNull(response.getBrandImageColored());
+ assertNull(response.getBrandImageWhite());
+ assertNull(response.getBrandedFavicon());
+ assertNull(response.getBrandImageColoredUrl());
+ assertNull(response.getBrandImageWhiteUrl());
+ assertNull(response.getBrandedFaviconUrl());
+ }
+
+ @Test
+ public void testInvalidDomain() {
+ Theme parsed = Theme.fromJson("{\"domain\":\"Nexmo Business Meetings\"}");
+ assertNull(parsed.getDomain());
+ }
+
+ @Test
+ public void testSerializeEmpty() {
+ String json = Theme.builder().build().toJson();
+ assertEquals("{}", json);
+ }
+
+ @Test
+ public void testBrandTextValidation() {
+ String padding = IntStream.range(1, 200)
+ .mapToObj(i -> Character.toString((char) (((char) i) % 100)))
+ .collect(Collectors.joining());
+
+ assertEquals(199, Theme.builder().brandText(padding).build().getBrandText().length());
+ assertEquals(200, Theme.builder().brandText("0"+padding).build().getBrandText().length());
+ assertThrows(IllegalArgumentException.class, () -> Theme.builder().brandText("ab"+padding).build());
+ assertThrows(IllegalArgumentException.class, () -> Theme.builder().brandText(" ").build());
+ }
+
+ @Test
+ public void testThemeNameValidation() {
+ String padding = IntStream.range(1, 200)
+ .mapToObj(i -> Character.toString((char) (((char) i) % 126)))
+ .collect(Collectors.joining());
+
+ assertEquals(199, Theme.builder().themeName(padding).build().getThemeName().length());
+ assertEquals(200, Theme.builder().themeName("0"+padding).build().getThemeName().length());
+ assertThrows(IllegalArgumentException.class, () -> Theme.builder().themeName("ab"+padding).build());
+ assertThrows(IllegalArgumentException.class, () -> Theme.builder().themeName(" ").build());
+ }
+
+ @Test
+ public void testShortCompanyUrlValidation() {
+ String padding = IntStream.range(1, 128)
+ .mapToObj(i -> Character.toString((char) (((char) i) % 126)))
+ .collect(Collectors.joining());
+
+ assertEquals(127, Theme.builder().shortCompanyUrl(padding).build().getShortCompanyUrl().length());
+ assertEquals(128, Theme.builder().shortCompanyUrl("0"+padding).build().getShortCompanyUrl().length());
+ assertThrows(IllegalArgumentException.class, () -> Theme.builder().shortCompanyUrl("ab"+padding).build());
+ assertThrows(IllegalArgumentException.class, () -> Theme.builder().shortCompanyUrl(" ").build());
+ }
+
+ @Test
+ public void testMainColorValidation() {
+ assertThrows(IllegalArgumentException.class, () -> Theme.builder().mainColor(" ").build());
+ assertThrows(IllegalArgumentException.class, () -> Theme.builder().mainColor("").build());
+ assertThrows(IllegalArgumentException.class, () -> Theme.builder().mainColor("#######").build());
+ assertThrows(IllegalArgumentException.class, () -> Theme.builder().mainColor("#gggggg").build());
+ }
+
+ @Test(expected = VonageUnexpectedException.class)
+ public void triggerJsonProcessingException() {
+ class SelfRefrencing extends Theme {
+ @JsonProperty("self") final SelfRefrencing self = this;
+ }
+ new SelfRefrencing().toJson();
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/vonage/client/meetings/UpdateApplicationEndpointTest.java b/src/test/java/com/vonage/client/meetings/UpdateApplicationEndpointTest.java
new file mode 100644
index 000000000..971c10a6c
--- /dev/null
+++ b/src/test/java/com/vonage/client/meetings/UpdateApplicationEndpointTest.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2023 Vonage
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.vonage.client.meetings;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.vonage.client.*;
+import com.vonage.client.auth.JWTAuthMethod;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.RequestBuilder;
+import org.apache.http.entity.ContentType;
+import org.apache.http.util.EntityUtils;
+import static org.junit.Assert.assertEquals;
+import org.junit.Before;
+import org.junit.Test;
+import java.util.UUID;
+
+public class UpdateApplicationEndpointTest {
+ private UpdateApplicationEndpoint endpoint;
+
+ @Before
+ public void setUp() {
+ endpoint = new UpdateApplicationEndpoint(new HttpWrapper(new JWTAuthMethod("app-id", new byte[0])));
+ }
+
+ @Test
+ public void testMakeRequest() throws Exception {
+ UUID themeId = UUID.fromString("e86a7335-35fe-45e1-b961-5777d4748022");
+ UpdateApplicationRequest request = UpdateApplicationRequest.builder()
+ .defaultThemeId(themeId)
+ .build();
+ RequestBuilder builder = endpoint.makeRequest(request);
+ assertEquals("PATCH", builder.getMethod());
+ String expectedUri = "https://api-eu.vonage.com/meetings/applications";
+ assertEquals(expectedUri, builder.build().getURI().toString());
+ assertEquals(ContentType.APPLICATION_JSON.getMimeType(), builder.getFirstHeader("Content-Type").getValue());
+ assertEquals(ContentType.APPLICATION_JSON.getMimeType(), builder.getFirstHeader("Accept").getValue());
+ String expectedRequest = "{\"update_details\": {\"default_theme_id\":\""+themeId+"\"}}";
+ assertEquals(expectedRequest, EntityUtils.toString(builder.getEntity()));
+ String expectedResponse = "{\n" +
+ " \"application_id\": \"48ac72d0-a829-4896-a067-dcb1c2b0f30c\",\n" +
+ " \"account_id\": \"\",\n" +
+ " \"default_theme_id\": \""+themeId+"\", \"unknown\": 1\n" +
+ "}";
+ HttpResponse mockResponse = TestUtils.makeJsonHttpResponse(200, expectedResponse);
+ Application parsed = endpoint.parseResponse(mockResponse);
+ assertEquals(themeId, parsed.getDefaultThemeId());
+ assertEquals("48ac72d0-a829-4896-a067-dcb1c2b0f30c", parsed.getApplicationId().toString());
+ assertEquals("", parsed.getAccountId());
+ }
+
+ @Test
+ public void testCustomUri() throws Exception {
+ String baseUri = "http://example.com";
+ HttpWrapper wrapper = new HttpWrapper(HttpConfig.builder().baseUri(baseUri).build());
+ endpoint = new UpdateApplicationEndpoint(wrapper);
+ String expectedUri = baseUri + "/meetings/applications";
+ UpdateApplicationRequest request = UpdateApplicationRequest.builder().defaultThemeId(UUID.randomUUID()).build();
+ RequestBuilder builder = endpoint.makeRequest(request);
+ assertEquals(expectedUri, builder.build().getURI().toString());
+ assertEquals(ContentType.APPLICATION_JSON.getMimeType(), builder.getFirstHeader("Content-Type").getValue());
+ assertEquals(ContentType.APPLICATION_JSON.getMimeType(), builder.getFirstHeader("Accept").getValue());
+ assertEquals("PATCH", builder.getMethod());
+ }
+
+ @Test(expected = MeetingsResponseException.class)
+ public void testUnsuccessfulResponse() throws Exception {
+ endpoint.parseResponse(TestUtils.makeJsonHttpResponse(400, "{}"));
+ }
+
+ @Test(expected = VonageUnexpectedException.class)
+ public void triggerJsonProcessingException() {
+ class SelfRefrencing extends UpdateApplicationRequest {
+ @JsonProperty("self") final SelfRefrencing self = this;
+ SelfRefrencing() {
+ super(UpdateApplicationRequest.builder().defaultThemeId(UUID.randomUUID()));
+ }
+ }
+ new SelfRefrencing().toJson();
+ }
+
+ @Test(expected = VonageResponseParseException.class)
+ public void testParseMalformedResponse() throws Exception {
+ endpoint.parseResponse(TestUtils.makeJsonHttpResponse(200, "{malformed]"));
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/vonage/client/meetings/UpdateRoomEndpointTest.java b/src/test/java/com/vonage/client/meetings/UpdateRoomEndpointTest.java
new file mode 100644
index 000000000..6bd9b4dd0
--- /dev/null
+++ b/src/test/java/com/vonage/client/meetings/UpdateRoomEndpointTest.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2023 Vonage
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.vonage.client.meetings;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.vonage.client.HttpConfig;
+import com.vonage.client.HttpWrapper;
+import com.vonage.client.TestUtils;
+import com.vonage.client.VonageUnexpectedException;
+import com.vonage.client.auth.JWTAuthMethod;
+import org.apache.http.client.methods.RequestBuilder;
+import org.apache.http.entity.ContentType;
+import org.apache.http.util.EntityUtils;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import org.junit.Before;
+import org.junit.Test;
+import java.time.Instant;
+import java.util.UUID;
+
+public class UpdateRoomEndpointTest {
+ private UpdateRoomEndpoint endpoint;
+
+ @Before
+ public void setUp() {
+ endpoint = new UpdateRoomEndpoint(new HttpWrapper(new JWTAuthMethod("app-id", new byte[0])));
+ }
+
+ @Test
+ public void testMakeRequestAllParameters() throws Exception {
+ UUID roomId = MeetingsClientTest.ROOM_ID, themeId = UUID.fromString("5af77e5e-410d-489c-a30e-21aaf8482715");
+ UpdateRoomRequest request = UpdateRoomRequest.builder()
+ .expireAfterUse(false)
+ .initialJoinOptions(InitialJoinOptions.builder().microphoneState(MicrophoneState.ON).build())
+ .callbackUrls(CallbackUrls.builder().build())
+ .availableFeatures(AvailableFeatures.builder().build())
+ .themeId(themeId)
+ .joinApprovalLevel(JoinApprovalLevel.AFTER_OWNER_ONLY)
+ .expiresAt(Instant.MAX.minusSeconds(86400))
+ .build();
+
+ request.roomId = roomId;
+ RequestBuilder builder = endpoint.makeRequest(request);
+ assertEquals("PATCH", builder.getMethod());
+ String expectedUri = "https://api-eu.vonage.com/meetings/rooms/"+roomId;
+ assertEquals(expectedUri, builder.build().getURI().toString());
+
+ assertEquals(ContentType.APPLICATION_JSON.getMimeType(), builder.getFirstHeader("Content-Type").getValue());
+ String expectedRequest = "{\"update_details\":{\"expire_after_use\":false,\"initial_join_options\":{\"microphone_state\":\"on\"}," +
+ "\"callback_urls\":{},\"available_features\":{},\"join_approval_level\":\"after_owner_only\"," +
+ "\"theme_id\":\"5af77e5e-410d-489c-a30e-21aaf8482715\",\"expires_at\":\"+1000000000-12-30T23:59:59.999Z\"}}";
+ assertEquals(expectedRequest, EntityUtils.toString(builder.getEntity()));
+ assertEquals(ContentType.APPLICATION_JSON.getMimeType(), builder.getFirstHeader("Accept").getValue());
+ MeetingRoom parsed = endpoint.parseResponse(TestUtils.makeJsonHttpResponse(200, "{}"));
+ assertNotNull(parsed);
+ }
+
+ @Test
+ public void testCustomUri() throws Exception {
+ String baseUri = "http://example.com";
+ HttpWrapper wrapper = new HttpWrapper(HttpConfig.builder().baseUri(baseUri).build());
+ endpoint = new UpdateRoomEndpoint(wrapper);
+ String expectedUri = baseUri + "/meetings/rooms/"+MeetingsClientTest.RANDOM_ID;
+ UpdateRoomRequest request = UpdateRoomRequest.builder().build();
+ request.roomId = MeetingsClientTest.RANDOM_ID;
+ RequestBuilder builder = endpoint.makeRequest(request);
+ assertEquals(expectedUri, builder.build().getURI().toString());
+ assertEquals(ContentType.APPLICATION_JSON.getMimeType(), builder.getFirstHeader("Content-Type").getValue());
+ assertEquals("{\"update_details\":{}}", EntityUtils.toString(builder.getEntity()));
+ assertEquals(ContentType.APPLICATION_JSON.getMimeType(), builder.getFirstHeader("Accept").getValue());
+ assertEquals("PATCH", builder.getMethod());
+ }
+
+ @Test(expected = MeetingsResponseException.class)
+ public void testUnsuccessfulResponse() throws Exception {
+ endpoint.parseResponse(TestUtils.makeJsonHttpResponse(400, "{}"));
+ }
+
+ @Test(expected = VonageUnexpectedException.class)
+ public void triggerJsonProcessingException() {
+ class SelfRefrencing extends UpdateRoomRequest {
+ @JsonProperty("self") final SelfRefrencing self = this;
+ SelfRefrencing() {
+ super(UpdateRoomRequest.builder());
+ }
+ }
+ new SelfRefrencing().toJson();
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/vonage/client/meetings/UpdateThemeEndpointTest.java b/src/test/java/com/vonage/client/meetings/UpdateThemeEndpointTest.java
new file mode 100644
index 000000000..330c8dcfd
--- /dev/null
+++ b/src/test/java/com/vonage/client/meetings/UpdateThemeEndpointTest.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2023 Vonage
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.vonage.client.meetings;
+
+import com.vonage.client.HttpConfig;
+import com.vonage.client.HttpWrapper;
+import com.vonage.client.TestUtils;
+import com.vonage.client.auth.JWTAuthMethod;
+import org.apache.http.client.methods.RequestBuilder;
+import org.apache.http.entity.ContentType;
+import org.apache.http.util.EntityUtils;
+import static org.junit.Assert.*;
+import org.junit.Before;
+import org.junit.Test;
+import java.util.UUID;
+
+public class UpdateThemeEndpointTest {
+ private UpdateThemeEndpoint endpoint;
+
+ @Before
+ public void setUp() {
+ endpoint = new UpdateThemeEndpoint(new HttpWrapper(new JWTAuthMethod("app-id", new byte[0])));
+ }
+
+ @Test
+ public void testMakeRequestAllParameters() throws Exception {
+ String themeId = "2f310f5a-995c-4f7e-9072-6aafbf6a4205";
+ Theme request = Theme.builder()
+ .shortCompanyUrl("developer.vonage.com").brandText("Vonage (purple)")
+ .themeName("Vonage theme").mainColor("#8a1278").build();
+
+ request.themeId = UUID.fromString(themeId);
+ RequestBuilder builder = endpoint.makeRequest(request);
+ assertEquals("PATCH", builder.getMethod());
+ String expectedUri = "https://api-eu.vonage.com/meetings/themes/"+themeId;
+ assertEquals(expectedUri, builder.build().getURI().toString());
+
+ assertEquals(ContentType.APPLICATION_JSON.getMimeType(), builder.getFirstHeader("Content-Type").getValue());
+ String expectedRequest = "{\"update_details\":{\"theme_name\":\"Vonage theme\",\"main_color\":" +
+ "\"#8a1278\",\"short_company_url\":\"developer.vonage.com\",\"brand_text\":\"Vonage (purple)\"}}";
+ assertEquals(expectedRequest, EntityUtils.toString(builder.getEntity()));
+ assertEquals(ContentType.APPLICATION_JSON.getMimeType(), builder.getFirstHeader("Accept").getValue());
+ String expectedResponse = "{\"nonsense\":false,\"brand_image_colored_url\":\"ftp://example.com/logo.png\"}";
+ Theme parsed = endpoint.parseResponse(TestUtils.makeJsonHttpResponse(200, expectedResponse));
+ assertNotNull(parsed);
+ assertEquals(request, parsed);
+ assertEquals("ftp://example.com/logo.png", parsed.getBrandImageColoredUrl().toString());
+ }
+
+ @Test
+ public void testCustomUri() throws Exception {
+ String baseUri = "http://example.com";
+ HttpWrapper wrapper = new HttpWrapper(HttpConfig.builder().baseUri(baseUri).build());
+ endpoint = new UpdateThemeEndpoint(wrapper);
+ Theme request = Theme.builder().build();
+ request.themeId = UUID.randomUUID();
+ String expectedUri = baseUri + "/meetings/themes/"+request.themeId;
+ RequestBuilder builder = endpoint.makeRequest(request);
+ assertEquals(expectedUri, builder.build().getURI().toString());
+ assertEquals(ContentType.APPLICATION_JSON.getMimeType(), builder.getFirstHeader("Content-Type").getValue());
+ assertEquals("{\"update_details\":{}}", EntityUtils.toString(builder.getEntity()));
+ assertEquals(ContentType.APPLICATION_JSON.getMimeType(), builder.getFirstHeader("Accept").getValue());
+ assertEquals("PATCH", builder.getMethod());
+ }
+
+ @Test
+ public void testParseResponseWithoutRequest() throws Exception {
+ Theme parsed = endpoint.parseResponse(TestUtils.makeJsonHttpResponse(200, "{}"));
+ assertNotNull(parsed);
+ }
+
+ @Test(expected = MeetingsResponseException.class)
+ public void testUnsuccessfulResponse() throws Exception {
+ endpoint.parseResponse(TestUtils.makeJsonHttpResponse(400, "{}"));
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/vonage/client/messages/MessageRequestTest.java b/src/test/java/com/vonage/client/messages/MessageRequestTest.java
index 556b800c6..b59ce2272 100644
--- a/src/test/java/com/vonage/client/messages/MessageRequestTest.java
+++ b/src/test/java/com/vonage/client/messages/MessageRequestTest.java
@@ -185,7 +185,7 @@ public void testToString() {
@Test(expected = VonageUnexpectedException.class)
public void triggerJsonProcessingException() {
class SelfRefrencing extends ConcreteMessageRequest {
- @JsonProperty("self") SelfRefrencing self = this;
+ @JsonProperty("self") final SelfRefrencing self = this;
SelfRefrencing() {
super(builder(MessageType.TEXT, Channel.SMS).from("447900000009").to("12002009000"));
diff --git a/src/test/java/com/vonage/client/messages/MessageStatusTest.java b/src/test/java/com/vonage/client/messages/MessageStatusTest.java
index 4bb81e894..a581ee215 100644
--- a/src/test/java/com/vonage/client/messages/MessageStatusTest.java
+++ b/src/test/java/com/vonage/client/messages/MessageStatusTest.java
@@ -171,7 +171,7 @@ public void testFromJsonInvalid() {
@Test(expected = VonageUnexpectedException.class)
public void triggerJsonProcessingException() {
class SelfRefrencing extends MessageStatus {
- @JsonProperty("self") SelfRefrencing self = this;
+ @JsonProperty("self") final SelfRefrencing self = this;
}
new SelfRefrencing().toJson();
}
diff --git a/src/test/java/com/vonage/client/proactiveconnect/ContactsListTest.java b/src/test/java/com/vonage/client/proactiveconnect/ContactsListTest.java
index 257a69bc8..6fa04bc47 100644
--- a/src/test/java/com/vonage/client/proactiveconnect/ContactsListTest.java
+++ b/src/test/java/com/vonage/client/proactiveconnect/ContactsListTest.java
@@ -144,7 +144,7 @@ public void testFromJsonEmpty() {
@Test(expected = VonageUnexpectedException.class)
public void triggerJsonProcessingException() {
class SelfRefrencing extends ContactsList {
- @JsonProperty("self") SelfRefrencing self = this;
+ @JsonProperty("self") final SelfRefrencing self = this;
}
new SelfRefrencing().toJson();
}
diff --git a/src/test/java/com/vonage/client/subaccounts/CreateSubaccountRequestTest.java b/src/test/java/com/vonage/client/subaccounts/CreateSubaccountRequestTest.java
index fb774d61b..08c747a71 100644
--- a/src/test/java/com/vonage/client/subaccounts/CreateSubaccountRequestTest.java
+++ b/src/test/java/com/vonage/client/subaccounts/CreateSubaccountRequestTest.java
@@ -74,7 +74,7 @@ public void testNameLength() {
@Test(expected = VonageUnexpectedException.class)
public void triggerJsonProcessingException() {
class SelfRefrencing extends CreateSubaccountRequest {
- @JsonProperty("self") SelfRefrencing self = this;
+ @JsonProperty("self") final SelfRefrencing self = this;
SelfRefrencing(Builder builder) {
super(builder);
diff --git a/src/test/java/com/vonage/client/subaccounts/NumberTransferTest.java b/src/test/java/com/vonage/client/subaccounts/NumberTransferTest.java
index 94752431b..efabd44ac 100644
--- a/src/test/java/com/vonage/client/subaccounts/NumberTransferTest.java
+++ b/src/test/java/com/vonage/client/subaccounts/NumberTransferTest.java
@@ -99,7 +99,7 @@ public void testFromJsonEmpty() {
@Test(expected = VonageUnexpectedException.class)
public void triggerJsonProcessingException() {
class SelfRefrencing extends AbstractTransfer {
- @JsonProperty("self") SelfRefrencing self = this;
+ @JsonProperty("self") final SelfRefrencing self = this;
}
new SelfRefrencing().toJson();
}
diff --git a/src/test/java/com/vonage/client/subaccounts/UpdateSubaccountRequestTest.java b/src/test/java/com/vonage/client/subaccounts/UpdateSubaccountRequestTest.java
index 69a471c3e..1a8eadcc6 100644
--- a/src/test/java/com/vonage/client/subaccounts/UpdateSubaccountRequestTest.java
+++ b/src/test/java/com/vonage/client/subaccounts/UpdateSubaccountRequestTest.java
@@ -70,7 +70,7 @@ public void testNameLength() {
@Test(expected = VonageUnexpectedException.class)
public void triggerJsonProcessingException() {
class SelfRefrencing extends UpdateSubaccountRequest {
- @JsonProperty("self") SelfRefrencing self = this;
+ @JsonProperty("self") final SelfRefrencing self = this;
SelfRefrencing(Builder builder) {
super(builder);
diff --git a/src/test/java/com/vonage/client/verify2/VerificationRequestTest.java b/src/test/java/com/vonage/client/verify2/VerificationRequestTest.java
index 7be3c20d1..a474fe0d5 100644
--- a/src/test/java/com/vonage/client/verify2/VerificationRequestTest.java
+++ b/src/test/java/com/vonage/client/verify2/VerificationRequestTest.java
@@ -286,7 +286,7 @@ public void testFraudCheckOnlyIncludedIfFalse() {
@Test(expected = VonageUnexpectedException.class)
public void triggerJsonProcessingException() {
class SelfRefrencing extends VerificationRequest {
- @JsonProperty("self") SelfRefrencing self = this;
+ @JsonProperty("self") final SelfRefrencing self = this;
SelfRefrencing(Builder builder) {
super(builder);
diff --git a/src/test/java/com/vonage/client/verify2/VerifyCodeEndpointTest.java b/src/test/java/com/vonage/client/verify2/VerifyCodeEndpointTest.java
index ef601e628..1a57bf125 100644
--- a/src/test/java/com/vonage/client/verify2/VerifyCodeEndpointTest.java
+++ b/src/test/java/com/vonage/client/verify2/VerifyCodeEndpointTest.java
@@ -117,7 +117,7 @@ public void testCustomUri() throws Exception {
@Test(expected = VonageUnexpectedException.class)
public void triggerJsonProcessingException() {
class SelfRefrencing extends VerifyCodeRequestWrapper {
- @JsonProperty("self") SelfRefrencing self = this;
+ @JsonProperty("self") final SelfRefrencing self = this;
SelfRefrencing() {
super(null, null);
}