Skip to content
Open
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
180 changes: 180 additions & 0 deletions CallAutomation_AppointmentBooking/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.0.6</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>

<groupId>com.communication.callautomation</groupId>
<artifactId>AppointmentBooking</artifactId>
<version>1.0-SNAPSHOT</version>

<name>AppointmentBooking</name>
<description>CallAutomation Sample application for instructional usage</description>

<properties>
<maven.compiler.source>18</maven.compiler.source>
<maven.compiler.target>18</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<lombok.version>1.18.26</lombok.version>
</properties>

<repositories>
<repository>
<id>test-feed</id>
<url>https://pkgs.dev.azure.com/msazuredev/6bd2c509-7068-4110-a280-4902014c36cb/_packaging/test-feed/maven/v1</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>com.vaadin.external.google</groupId>
<artifactId>android-json</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-core</artifactId>
<version>1.39.0</version>
</dependency>
<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-identity</artifactId>
<version>1.9.0</version>
</dependency>
<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-communication-identity</artifactId>
<version>1.4.5</version>
<exclusions>
<exclusion>
<groupId>com.azure</groupId>
<artifactId>azure-communication-common</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-communication-callautomation</artifactId>
<version>1.0.0-alpha.20230517.2</version>
</dependency>
<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-messaging-eventgrid</artifactId>
<version>4.15.1</version>
</dependency>
<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-communication-common</artifactId>
<version>2.0.0-beta.1</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.10</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
<version>${lombok.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20230227</version>
</dependency>
</dependencies>

<build>
<pluginManagement>
<plugins>
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.2.0</version>
</plugin>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.3.1</version>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<version>3.3.0</version>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>3.1.1</version>
</plugin>
<plugin>
<artifactId>maven-site-plugin</artifactId>
<version>3.12.1</version>
</plugin>
<plugin>
<artifactId>maven-project-info-reports-plugin</artifactId>
<version>3.4.3</version>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<goals>
<goal>java</goal>
</goals>
</execution>
</executions>
<configuration>
<mainClass>com.communication.callautomation.Main</mainClass>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.communication.callautomation;

import com.azure.core.http.HttpClient;
import com.communication.callautomation.config.AcsConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
@EnableConfigurationProperties(value = AcsConfig.class)
public class Main {

@Bean
HttpClient azureHttpClient() {
return HttpClient.createDefault();
}
public static void main(String[] args) {
SpringApplication.run(Main.class, args);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package com.communication.callautomation.acs.client;

import com.azure.communication.callautomation.models.*;
import com.azure.communication.common.CommunicationIdentifier;
import com.azure.core.http.rest.Response;
import com.azure.core.util.Context;
import com.communication.callautomation.config.AcsConfig;
import com.communication.callautomation.core.CallAutomationService;
import com.communication.callautomation.core.model.EventInfo;
import com.communication.callautomation.exceptions.AzureCallAutomationException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;

import java.time.Duration;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;

@Component
@Slf4j
public class AcsClient implements CallAutomationService {
private final AcsConfig acsConfig;
private final CallAutomationClientFactory callAutomationClientFactory;

@Autowired
public AcsClient(final AcsConfig acsConfig,
final CallAutomationClientFactory callAutomationClientFactory) {
this.acsConfig = acsConfig;
this.callAutomationClientFactory = callAutomationClientFactory;
}

@Override
public String answerCall(final EventInfo eventInfo) {
String incomingCallContext = eventInfo.getIncomingCallContext();
String callbackUri = acsConfig.getCallbackUri(eventInfo.getFromId());
String correlationId = eventInfo.getCorrelationId();

log.info("Answering media call with following callbackuri: {}", callbackUri);

try{
AnswerCallResult answerCallResponse = callAutomationClientFactory.getCallAutomationClient(correlationId)
.answerCall(incomingCallContext, callbackUri);
return answerCallResponse.getCallConnectionProperties().getCallConnectionId();
} catch(Exception e) {
log.error("Error occurred when Answering the call");
throw new AzureCallAutomationException(e.getMessage(), e);
}
}

@Override
public String startRecording(final EventInfo eventInfo) {
try {
ServerCallLocator serverCallLocator = new ServerCallLocator(callAutomationClientFactory
.getCallAutomationClient(eventInfo.getCorrelationId())
.getCallConnection(eventInfo.getCallConnectionId())
.getCallProperties()
.getServerCallId());

StartRecordingOptions recordingOptions = new StartRecordingOptions(serverCallLocator);

Response<RecordingStateResult> response = callAutomationClientFactory.getCallAutomationClient(eventInfo.getCorrelationId())
.getCallRecording()
.startWithResponse(recordingOptions, Context.NONE);
String recordingId = response.getValue().getRecordingId();
log.info("Start Recording with recording ID: {}", recordingId);
return "Start Recording operation finished";
} catch(Exception e) {
log.error("Recording operation failed {} {}", e.getMessage(), e.getCause());
throw new AzureCallAutomationException(e.getMessage(), e);
}
}

@Override
public String playAudio(final EventInfo eventInfo, final String prompt) {
List<CommunicationIdentifier> listTargets = Arrays.asList(CommunicationIdentifier.fromRawId(eventInfo.getFromId()));
PlaySource playSource = new FileSource().setUrl(acsConfig.getMediaUri(prompt));
PlayOptions playOptions = new PlayOptions(playSource, listTargets);
log.info("Play audio operation started");
try {
Response response = callAutomationClientFactory.getCallAutomationClient(eventInfo.getCorrelationId())
.getCallConnection(eventInfo.getCallConnectionId())
.getCallMedia()
.playWithResponse(playOptions, Context.NONE);
return "Play audio operation finished";
} catch(Exception e) {
log.error("Error when Playing audio to participant {} {}", e.getMessage(), e.getCause());
throw new AzureCallAutomationException(e.getMessage(), e);
}
}

@Override
public String singleDigitDtmfRecognitionWithPrompt(final EventInfo eventInfo, final String prompt) {
CommunicationIdentifier rectarget = CommunicationIdentifier.fromRawId(eventInfo.getFromId());
PlaySource playSource = new FileSource().setUrl(acsConfig.getMediaUri(prompt));
CallMediaRecognizeDtmfOptions recognizeDtmfOptions = new CallMediaRecognizeDtmfOptions(rectarget, 1);
recognizeDtmfOptions.setInterToneTimeout(Duration.ofSeconds(10))
.setInitialSilenceTimeout(Duration.ofSeconds(15))
.setInterruptPrompt(true)
.setPlayPrompt(playSource);
log.info("DTMF Recognition operation started");
try {
Response response = callAutomationClientFactory.getCallAutomationClient(eventInfo.getCorrelationId())
.getCallConnection(eventInfo.getCallConnectionId())
.getCallMedia()
.startRecognizingWithResponse(recognizeDtmfOptions, Context.NONE);
return "DTMF Recognition operation ended";
} catch(Exception e) {
log.error("DTMF Recognition operation failed {} {}", e.getMessage(), e.getCause());
throw new AzureCallAutomationException(e.getMessage(), e);
}
}

@Override
public String terminateCall(final EventInfo eventInfo) {
log.info("Terminating the call");
try {
callAutomationClientFactory.getCallAutomationClient(eventInfo.getCorrelationId())
.getCallConnection(eventInfo.getCallConnectionId())
.hangUp(true);
return "HangUp call for all participants operation ended";
} catch(Exception e) {
log.error("HangUp call for all participants operation failed {} {}", e.getMessage(), e.getCause());
throw new AzureCallAutomationException(e.getMessage(), e);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.communication.callautomation.acs.client;

import com.azure.communication.callautomation.CallAutomationClient;

public interface CallAutomationClientFactory {
CallAutomationClient getCallAutomationClient(final String correlationId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package com.communication.callautomation.acs.client;

import com.azure.communication.callautomation.CallAutomationAsyncClient;
import com.azure.communication.callautomation.CallAutomationClient;
import com.azure.communication.callautomation.CallAutomationClientBuilder;
import com.azure.core.http.HttpClient;
import com.communication.callautomation.config.AcsConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@Component
@Slf4j
public class CallAutomationClientImpl implements CallAutomationClientFactory {
private final AcsConfig acsConfig;
private final HttpClient azureHttpClient;
private final Map<String, CallAutomationClient> clientMap = new ConcurrentHashMap<String, CallAutomationClient>();

@Autowired
public CallAutomationClientImpl(final AcsConfig acsConfig,
final HttpClient azureHttpClient) {
this.acsConfig = acsConfig;
this.azureHttpClient = azureHttpClient;
}
@Override
public CallAutomationClient getCallAutomationClient(final String correlationId) {
log.debug("Start: getCallAutomationClient");
String connectionString = acsConfig.getConnectionString();
CallAutomationClient callAutomationClient;
callAutomationClient = clientMap.get(correlationId);
if (callAutomationClient == null) {
callAutomationClient = createCallAutomationClient(connectionString, correlationId);
}
log.debug("End: getCallAutomationClient");
return callAutomationClient;
}

private synchronized CallAutomationClient createCallAutomationClient(final String connectionString, final String correlationId) {
log.debug("Start: createCallAutomationClient");
CallAutomationClientBuilder callAutomationClientBuilder = new CallAutomationClientBuilder()
.httpClient(azureHttpClient)
.connectionString(connectionString);
CallAutomationClient callAutomationClient = callAutomationClientBuilder.buildClient();
clientMap.put(correlationId, callAutomationClient);
log.debug("End: createCallAutomationClient");
return callAutomationClient;
}
}
Loading