Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DEVEXP-215: SAVML helpers shared by custom callouts requests and webhooks #42

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.sinch.sdk.domains.common.adapters.converters;

import java.time.Instant;
import java.time.OffsetDateTime;

public class OffsetDateTimeDtoConverter {

public static Instant convert(OffsetDateTime dto) {
return null != dto ? dto.toInstant() : null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ boolean validateAuthenticatedRequest(
VerificationEvent unserializeVerificationEvent(String jsonPayload) throws ApiMappingException;

/**
* This function can be called to serialize a verification response to be send as JSON
* This function can be called to serialize a verification response to be sent as JSON
*
* @param response The response to be serialized
* @return The JSON string to be sent
Expand Down
7 changes: 3 additions & 4 deletions client/src/main/com/sinch/sdk/domains/voice/CallsService.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package com.sinch.sdk.domains.voice;

import com.sinch.sdk.domains.voice.models.CallLegType;
import com.sinch.sdk.domains.voice.models.requests.CallsUpdateRequestParameters;
import com.sinch.sdk.domains.voice.models.response.CallInformation;
import com.sinch.sdk.domains.voice.models.svaml.SVAMLControl;

/**
* Using the Calls service, you can manage on-going calls or retrieve information about a call.
Expand Down Expand Up @@ -37,7 +37,7 @@ public interface CallsService {
* @param callId The unique identifier of the call. This value is generated by the system
* @param parameters Tasks to be used related to this call
*/
void update(String callId, CallsUpdateRequestParameters parameters);
void update(String callId, SVAMLControl parameters);

/**
* This method is used to manage ongoing, connected calls. This method is only used when using the
Expand All @@ -51,6 +51,5 @@ public interface CallsService {
* @param callId The unique identifier of the call. This value is generated by the system
* @param parameters Tasks to be used related to this call
*/
void manageWithCallLeg(
String callId, CallLegType callLeg, CallsUpdateRequestParameters parameters);
void manageWithCallLeg(String callId, CallLegType callLeg, SVAMLControl parameters);
}
8 changes: 8 additions & 0 deletions client/src/main/com/sinch/sdk/domains/voice/VoiceService.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,12 @@ public interface VoiceService {
* @since 1.0
*/
ApplicationsService applications();

/**
* Webhooks helpers instance
*
* @return instance service related to webhooks helpers
* @since 1.0
*/
WebHooksService webhooks();
}
59 changes: 59 additions & 0 deletions client/src/main/com/sinch/sdk/domains/voice/WebHooksService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.sinch.sdk.domains.voice;

import com.sinch.sdk.core.exceptions.ApiMappingException;
import com.sinch.sdk.domains.voice.models.svaml.SVAMLControl;
import com.sinch.sdk.domains.voice.models.webhooks.WebhooksEvent;
import java.util.Map;

/**
* Webhooks service
*
* @see <a
* href="https://developers.sinch.com/docs/voice/api-reference/voice/tag/Callbacks">https://developers.sinch.com/docs/voice/api-reference/voice/tag/Callbacks/</a>
* @since 1.0
*/
public interface WebHooksService {

/**
* The Sinch Platform can initiate callback requests to a URL you define (Callback URL) on request
* and result events. All callback requests are signed using your Application key and secret pair
* found on your dashboard. The signature is included in the Authorization header of the request
*
* <p>By using following function, you can ensure authentication according to received payload
* from your backend
*
* @param method The HTTP method used ot handle the callback
* @param path The path to you backend endpoint used for callback
* @param headers Received headers
* @param jsonPayload Received payload
* @return Is authentication is validated (true) or not (false)
* <p>see <a
* href="https://developers.sinch.com/docs/voice/api-reference/authentication/callback-signed-request">https://developers.sinch.com/docs/voice/api-reference/authentication/callback-signed-request/</a>
* @since 1.0
*/
boolean validateAuthenticatedRequest(
String method, String path, Map<String, String> headers, String jsonPayload);

/**
* This function can be called to deserialize received payload onto callback onto proper java
* Voice event class
*
* @param jsonPayload Received payload to be deserialized
* @return The Voice event instance class
* <p>see <a
* href="https://developers.sinch.com/docs/voice/api-reference/voice/tag/Callbacks/">https://developers.sinch.com/docs/voice/api-reference/voice/tag/Callbacks/</a>
* @since 1.0
*/
WebhooksEvent unserializeWebhooksEvent(String jsonPayload) throws ApiMappingException;

/**
* This function can be called to serialize a Voice response to be sent as JSON
*
* @param response The response to be serialized
* @return The JSON string to be sent
* <p>see <a
* href="https://developers.sinch.com/docs/voice/api-reference/voice/tag/Callbacks/">https://developers.sinch.com/docs/voice/api-reference/voice/tag/Callbacks/</a>
* @since 1.0
*/
String serializeWebhooksResponse(SVAMLControl response) throws ApiMappingException;
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
import com.sinch.sdk.domains.voice.adapters.api.v1.CallsApi;
import com.sinch.sdk.domains.voice.adapters.converters.CallsDtoConverter;
import com.sinch.sdk.domains.voice.models.CallLegType;
import com.sinch.sdk.domains.voice.models.requests.CallsUpdateRequestParameters;
import com.sinch.sdk.domains.voice.models.response.CallInformation;
import com.sinch.sdk.domains.voice.models.svaml.SVAMLControl;
import com.sinch.sdk.models.Configuration;
import java.util.Map;

Expand All @@ -30,12 +30,11 @@ public CallInformation get(String callId) {
return CallsDtoConverter.convert(getApi().callingGetCallResult(callId));
}

public void update(String callId, CallsUpdateRequestParameters parameters) {
public void update(String callId, SVAMLControl parameters) {
getApi().callingUpdateCall(callId, CallsDtoConverter.convert(parameters));
}

public void manageWithCallLeg(
String callId, CallLegType callLeg, CallsUpdateRequestParameters parameters) {
public void manageWithCallLeg(String callId, CallLegType callLeg, SVAMLControl parameters) {
getApi()
.callingManageCallWithCallLeg(
callId, callLeg.value(), CallsDtoConverter.convert(parameters));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import com.sinch.sdk.core.http.HttpClient;
import com.sinch.sdk.domains.voice.ApplicationsService;
import com.sinch.sdk.domains.voice.CalloutsService;
import com.sinch.sdk.domains.voice.WebHooksService;
import com.sinch.sdk.models.Configuration;
import java.util.Map;
import java.util.Objects;
Expand All @@ -26,6 +27,7 @@ public class VoiceService implements com.sinch.sdk.domains.voice.VoiceService {
private ConferencesService conferences;
private CallsService calls;
private ApplicationsService applications;
private WebHooksService webhooks;

private Map<String, AuthManager> clientAuthManagers;
private Map<String, AuthManager> webhooksAuthManagers;
Expand Down Expand Up @@ -99,6 +101,15 @@ public ApplicationsService applications() {
return this.applications;
}

public WebHooksService webhooks() {
checkCredentials();
if (null == this.webhooks) {
this.webhooks =
new com.sinch.sdk.domains.voice.adapters.WebHooksService(webhooksAuthManagers);
}
return this.webhooks;
}

private void checkCredentials() throws ApiAuthException {
if (null == clientAuthManagers || clientAuthManagers.isEmpty()) {
throw new ApiAuthException(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package com.sinch.sdk.domains.voice.adapters;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.sinch.sdk.core.exceptions.ApiMappingException;
import com.sinch.sdk.core.http.AuthManager;
import com.sinch.sdk.core.utils.databind.Mapper;
import com.sinch.sdk.domains.voice.adapters.converters.CallsDtoConverter;
import com.sinch.sdk.domains.voice.adapters.converters.WebhooksEventDtoConverter;
import com.sinch.sdk.domains.voice.models.dto.v1.SVAMLRequestBodyDto;
import com.sinch.sdk.domains.voice.models.dto.v1.WebhooksEventDto;
import com.sinch.sdk.domains.voice.models.svaml.SVAMLControl;
import com.sinch.sdk.domains.voice.models.webhooks.WebhooksEvent;
import java.util.Map;
import java.util.TreeMap;
import java.util.logging.Logger;

public class WebHooksService implements com.sinch.sdk.domains.voice.WebHooksService {
private static final Logger LOGGER = Logger.getLogger(WebHooksService.class.getName());

private final Map<String, AuthManager> authManagers;

public WebHooksService(Map<String, AuthManager> authManagers) {
this.authManagers = authManagers;
}

public boolean validateAuthenticatedRequest(
String method, String path, Map<String, String> headers, String jsonPayload) {

// convert header keys to use case-insensitive map keys
Map<String, String> caseInsensitiveHeaders = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
caseInsensitiveHeaders.putAll(headers);

String authorizationHeader = caseInsensitiveHeaders.get("Authorization");

// no authorization required
if (null == authorizationHeader) {
return true;
}

String[] split = authorizationHeader.split(" ");
String authorizationKeyword = split.length > 0 ? split[0] : "";

AuthManager authManager = authManagers.get(authorizationKeyword);
if (null == authManager) {
// unknown auth manager
LOGGER.severe(
String.format("Auth manager for authorization '%s' not found", authorizationKeyword));
return false;
}
return authManager.validateAuthenticatedRequest(method, path, headers, jsonPayload);
}

@Override
public WebhooksEvent unserializeWebhooksEvent(String jsonPayload) throws ApiMappingException {
try {
WebhooksEventDto o = Mapper.getInstance().readValue(jsonPayload, WebhooksEventDto.class);
return WebhooksEventDtoConverter.convert(o);
} catch (JsonProcessingException e) {
throw new ApiMappingException(jsonPayload, e);
}
}

@Override
public String serializeWebhooksResponse(SVAMLControl response) throws ApiMappingException {
SVAMLRequestBodyDto dto = CallsDtoConverter.convert(response);
try {
return Mapper.getInstance().writeValueAsString(dto);
} catch (JsonProcessingException e) {
throw new ApiMappingException(response.toString(), e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import com.sinch.sdk.domains.voice.models.dto.v1.ConferenceCalloutRequestConferenceDtmfOptionsDto;
import com.sinch.sdk.domains.voice.models.dto.v1.ConferenceCalloutRequestDto;
import com.sinch.sdk.domains.voice.models.dto.v1.CustomCalloutRequestDto;
import com.sinch.sdk.domains.voice.models.dto.v1.DomainDto;
import com.sinch.sdk.domains.voice.models.dto.v1.GetCalloutResponseObjDto;
import com.sinch.sdk.domains.voice.models.dto.v1.TtsCalloutRequestDto;
import com.sinch.sdk.domains.voice.models.requests.CalloutRequestParameters;
Expand Down Expand Up @@ -56,7 +57,7 @@ private static CalloutRequestDto convert(CalloutRequestParametersConference clie
client.getEnablePie().ifPresent(dto::setEnablePie);
client.getLocale().ifPresent(dto::setLocale);
client.getGreeting().ifPresent(dto::setGreeting);
client.getMohClass().ifPresent(f -> dto.setMohClass(EnumDynamicConverter.convert(f)));
client.getMusicOnHold().ifPresent(f -> dto.setMohClass(EnumDynamicConverter.convert(f)));
client.getDomain().ifPresent(f -> dto.setDomain(EnumDynamicConverter.convert(f)));

return new CalloutRequestDto()
Expand All @@ -76,7 +77,9 @@ private static CalloutRequestDto convert(CalloutRequestParametersTTS client) {
client.getEnableDice().ifPresent(dto::setEnableDice);
client.getEnablePie().ifPresent(dto::setEnablePie);
client.getLocale().ifPresent(dto::setLocale);
client.getDomain().ifPresent(f -> dto.setDomain(EnumDynamicConverter.convert(f)));
client
.getDomain()
.ifPresent(f -> dto.setDomain(DomainDto.fromValue(EnumDynamicConverter.convert(f))));
client.getText().ifPresent(dto::setText);
client.getPrompts().ifPresent(dto::setPrompts);

Expand All @@ -93,9 +96,9 @@ private static CalloutRequestDto convert(CalloutRequestParametersCustom client)
client.getCustom().ifPresent(dto::setCustom);

client.getMaxDuration().ifPresent(dto::setMaxDuration);
client.getIce().ifPresent(dto::setIce);
client.getAce().ifPresent(dto::setAce);
client.getPie().ifPresent(dto::setPie);
client.getIce().ifPresent(f -> dto.setIce(ControlDtoConverter.convert(f)));
client.getAce().ifPresent(f -> dto.setAce(ControlDtoConverter.convert(f)));
client.getPie().ifPresent(f -> dto.setPie(ControlDtoConverter.convert(f)));

return new CalloutRequestDto().method(MethodEnum.CUSTOMCALLOUT.getValue()).customCallout(dto);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
package com.sinch.sdk.domains.voice.adapters.converters;

import com.sinch.sdk.domains.voice.models.CallReasonType;
import com.sinch.sdk.domains.voice.models.CallResultType;
import com.sinch.sdk.domains.voice.models.DomainType;
import com.sinch.sdk.domains.voice.models.dto.v1.GetCallResponseObjDto;
import com.sinch.sdk.domains.voice.models.dto.v1.SVAMLRequestBodyDto;
import com.sinch.sdk.domains.voice.models.requests.CallsUpdateRequestParameters;
import com.sinch.sdk.domains.voice.models.response.CallInformation;
import com.sinch.sdk.domains.voice.models.response.CallReasonType;
import com.sinch.sdk.domains.voice.models.response.CallResultType;
import com.sinch.sdk.domains.voice.models.response.CallStatusType;
import com.sinch.sdk.domains.voice.models.svaml.SVAMLControl;

public class CallsDtoConverter {

Expand All @@ -22,7 +22,7 @@ public static CallInformation convert(GetCallResponseObjDto dto) {
.setCallId(dto.getCallId())
.setDuration(dto.getDuration())
.setStatus(null != dto.getStatus() ? CallStatusType.from(dto.getStatus()) : null)
.setResult(null != dto.getResult() ? CallResultType.from(dto.getResult()) : null)
.setResult(null != dto.getResult() ? CallResultType.from(dto.getResult().getValue()) : null)
.setReason(null != dto.getReason() ? CallReasonType.from(dto.getReason()) : null)
.setTimeStamp(null != dto.getTimestamp() ? dto.getTimestamp().toInstant() : null)
.setCustom(null != dto.getCustom() ? dto.getCustom() : null)
Expand All @@ -31,15 +31,16 @@ public static CallInformation convert(GetCallResponseObjDto dto) {
.build();
}

public static SVAMLRequestBodyDto convert(CallsUpdateRequestParameters client) {
public static SVAMLRequestBodyDto convert(SVAMLControl client) {
if (null == client) {
return null;
}
SVAMLRequestBodyDto dto = new SVAMLRequestBodyDto();

dto.instructions(SAVMLInstructionDtoConverter.convert(client.getInstructions()));
dto.action(SAVMLActionDtoConverter.convert(client.getAction()));

client
.getInstructions()
.ifPresent(f -> dto.instructions(SVAMLInstructionDtoConverter.convert(f)));
client.getAction().ifPresent(f -> dto.action(SVAMLActionDtoConverter.convert(f)));
return dto;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public static ManageConferenceParticipantRequestDto convert(

ManageConferenceParticipantRequestDto dto = new ManageConferenceParticipantRequestDto();
client.getCommand().ifPresent(f -> dto.command(EnumDynamicConverter.convert(f)));
client.getMoh().ifPresent(f -> dto.moh(EnumDynamicConverter.convert(f)));
client.getMusicOnHold().ifPresent(f -> dto.moh(EnumDynamicConverter.convert(f)));
return dto;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.sinch.sdk.domains.voice.adapters.converters;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.sinch.sdk.core.exceptions.ApiMappingException;
import com.sinch.sdk.core.utils.databind.Mapper;
import com.sinch.sdk.domains.voice.models.requests.Control;
import com.sinch.sdk.domains.voice.models.requests.ControlUrl;
import com.sinch.sdk.domains.voice.models.svaml.SVAMLControl;
import java.util.logging.Logger;

public class ControlDtoConverter {

private static final Logger LOGGER = Logger.getLogger(SVAMLActionDtoConverter.class.getName());

public static String convert(Control client) {
if (null == client) {
return null;
}
String dto;
if (client instanceof SVAMLControl) {
SVAMLControl value = (SVAMLControl) client;
dto = convertControlToEscapedJSON(value);
} else if (client instanceof ControlUrl) {
ControlUrl value = (ControlUrl) client;
dto = value.getURL();
} else {
LOGGER.severe(String.format("Unexpected class '%s'", client.getClass()));
dto = client.toString();
}
return dto;
}

private static String convertControlToEscapedJSON(SVAMLControl client) {
try {
return Mapper.getInstance().writeValueAsString(CallsDtoConverter.convert(client));
} catch (JsonProcessingException e) {
throw new ApiMappingException(client.toString(), e);
}
}
}
Loading
Loading