All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.vonage.client.meetings.MeetingsClient Maven / Gradle / Ivy

/*
 *   Copyright 2024 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.auth.JWTAuthMethod;
import com.vonage.client.common.HalPageResponse;
import com.vonage.client.common.HttpMethod;
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.*;
import java.util.function.Function;

/**
 * Meetings API client.
 *
 * @deprecated Support for this API will be removed in the next major release.
 */
@Deprecated
public class MeetingsClient {
	HttpClient httpClient;

	final RestEndpoint listRooms, searchThemeRooms;
	final RestEndpoint getRoom;
	final RestEndpoint createRoom;
	final RestEndpoint updateRoom;
	final RestEndpoint listThemes;
	final RestEndpoint getTheme;
	final RestEndpoint createTheme, updateTheme;
	final RestEndpoint deleteTheme;
	final RestEndpoint listRecordings;
	final RestEndpoint getRecording;
	final RestEndpoint deleteRecording;
	final RestEndpoint listDialNumbers;
	final RestEndpoint updateApplication;
	final RestEndpoint finalizeLogos;
	final RestEndpoint getLogoUploadUrls;

	/**
	 * Constructor.
	 *
	 * @param wrapper (REQUIRED) shared HTTP wrapper object used for making REST calls.
	 */
	public MeetingsClient(HttpWrapper wrapper) {
		super();
		httpClient = wrapper.getHttpClient();

		@SuppressWarnings("unchecked")
		class Endpoint extends DynamicEndpoint {
			Endpoint(Function pathGetter, HttpMethod method, R... type) {
				super(DynamicEndpoint. builder(type)
					.authMethod(JWTAuthMethod.class).requestMethod(method)
					.responseExceptionType(MeetingsResponseException.class)
					.wrapper(wrapper).pathGetter((de, req) -> {
						String base = de.getHttpWrapper().getHttpConfig().getApiEuBaseUri();
						return base + "/v1/meetings/" + pathGetter.apply(req);
					})
				);
			}
		}

		listRooms = new Endpoint<>(req -> "rooms", HttpMethod.GET);
		getRoom = new Endpoint<>(roomId -> "rooms/" + roomId, HttpMethod.GET);
		createRoom = new Endpoint<>(req -> "rooms", HttpMethod.POST);
		updateRoom = new Endpoint<>(req -> "rooms/" + req.roomId, HttpMethod.PATCH);
		searchThemeRooms = new Endpoint<>(req -> "themes/" + req.themeId + "/rooms", HttpMethod.GET);
		listThemes = new Endpoint<>(req -> "themes", HttpMethod.GET);
		getTheme = new Endpoint<>(themeId -> "themes/" + themeId, HttpMethod.GET);
		createTheme = new Endpoint<>(req -> "themes", HttpMethod.POST);
		updateTheme = new Endpoint<>(theme -> "themes/" + theme.getThemeId(), HttpMethod.PATCH);
		deleteTheme = new Endpoint<>(req -> "themes/" + req.themeId, HttpMethod.DELETE);
		listRecordings = new Endpoint<>(sid -> "sessions/" + sid + "/recordings", HttpMethod.GET);
		getRecording = new Endpoint<>(rid -> "recordings/" + rid, HttpMethod.GET);
		deleteRecording = new Endpoint<>(rid -> "recordings/" + rid, HttpMethod.DELETE);
		listDialNumbers = new Endpoint<>(req -> "dial-in-numbers", HttpMethod.GET);
		updateApplication = new Endpoint<>(req -> "applications", HttpMethod.PATCH);
		finalizeLogos = new Endpoint<>(req -> "themes/" + req.themeId + "/finalizeLogos", HttpMethod.PUT);
		getLogoUploadUrls = new Endpoint<>(req -> "themes/logos-upload-urls", HttpMethod.GET);
	}

	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(
			RestEndpoint 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() <= response.getPageSize()) {
			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.getPageSize() >= 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 fully 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.setThemeIdAndFlagUpdate(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())
					.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()));
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy