Asterisk Java Integration in Java

Introduction

Asterisk Java integration enables Java applications to interact with Asterisk PBX (Private Branch Exchange) systems. This allows for building telephony applications, call control systems, IVR (Interactive Voice Response) systems, and real-time communication solutions.

Setup and Dependencies

Maven Dependencies

<dependencies>
<!-- Asterisk-Java - Main library -->
<dependency>
<groupId>org.asteriskjava</groupId>
<artifactId>asterisk-java</artifactId>
<version>3.43.0</version>
</dependency>
<!-- Spring Boot Starter (optional) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.7.0</version>
</dependency>
<!-- Logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.36</version>
</dependency>
<!-- Connection pooling -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.11.1</version>
</dependency>
</dependencies>

Basic Asterisk Manager Interface (AMI) Connection

Core Connection Management

package com.asterisk.manager;
import org.asteriskjava.manager.*;
import org.asteriskjava.manager.action.*;
import org.asteriskjava.manager.response.*;
import org.asteriskjava.manager.event.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
public class AsteriskManager {
private static final Logger logger = LoggerFactory.getLogger(AsteriskManager.class);
private ManagerConnection connection;
private final String host;
private final int port;
private final String username;
private final String password;
private boolean connected = false;
public AsteriskManager(String host, int port, String username, String password) {
this.host = host;
this.port = port;
this.username = username;
this.password = password;
initializeConnection();
}
private void initializeConnection() {
try {
ManagerConnectionFactory factory = new ManagerConnectionFactory(
host, port, username, password
);
this.connection = factory.createManagerConnection();
setupEventHandlers();
} catch (Exception e) {
logger.error("Failed to initialize Asterisk manager connection", e);
throw new RuntimeException("Asterisk connection initialization failed", e);
}
}
public void connect() {
if (connected) {
logger.warn("Already connected to Asterisk");
return;
}
try {
connection.login();
connected = true;
logger.info("Successfully connected to Asterisk server: {}:{}", host, port);
} catch (Exception e) {
logger.error("Failed to connect to Asterisk server", e);
throw new RuntimeException("Asterisk connection failed", e);
}
}
public void disconnect() {
if (!connected) {
return;
}
try {
connection.logoff();
connected = false;
logger.info("Disconnected from Asterisk server");
} catch (Exception e) {
logger.error("Error disconnecting from Asterisk", e);
}
}
public boolean isConnected() {
return connected && connection != null && connection.getState() == ManagerConnectionState.CONNECTED;
}
public ManagerResponse executeAction(ManagerAction action) {
if (!isConnected()) {
throw new IllegalStateException("Not connected to Asterisk server");
}
try {
ManagerResponse response = connection.sendAction(action);
logger.debug("Action executed successfully: {}", action.getAction());
return response;
} catch (Exception e) {
logger.error("Failed to execute action: {}", action.getAction(), e);
throw new RuntimeException("Action execution failed", e);
}
}
public CompletableFuture<ManagerResponse> executeActionAsync(ManagerAction action) {
return CompletableFuture.supplyAsync(() -> executeAction(action));
}
public void addEventListener(ManagerEventListener listener) {
connection.addEventListener(listener);
}
public void removeEventListener(ManagerEventListener listener) {
connection.removeEventListener(listener);
}
private void setupEventHandlers() {
// Add default event handlers
connection.addEventListener(new DefaultEventListener());
}
// Basic health check
public boolean healthCheck() {
try {
StatusAction statusAction = new StatusAction();
ManagerResponse response = executeAction(statusAction);
return response != null && response.getResponse().equals("Success");
} catch (Exception e) {
logger.warn("Asterisk health check failed", e);
return false;
}
}
private class DefaultEventListener implements ManagerEventListener {
@Override
public void onManagerEvent(ManagerEvent event) {
logger.debug("Received event: {}", event.getClass().getSimpleName());
// Handle specific events
if (event instanceof HangupEvent) {
handleHangupEvent((HangupEvent) event);
} else if (event instanceof NewStateEvent) {
handleNewStateEvent((NewStateEvent) event);
} else if (event instanceof DialEvent) {
handleDialEvent((DialEvent) event);
}
}
private void handleHangupEvent(HangupEvent event) {
logger.info("Call hung up - Channel: {}, Cause: {}", 
event.getChannel(), event.getCause());
}
private void handleNewStateEvent(NewStateEvent event) {
logger.debug("Channel state changed - Channel: {}, State: {}", 
event.getChannel(), event.getState());
}
private void handleDialEvent(DialEvent event) {
logger.info("Dial event - Source: {}, Destination: {}, Status: {}", 
event.getSrc(), event.getDestination(), event.getDialStatus());
}
}
}

Call Control Operations

Comprehensive Call Management

package com.asterisk.call;
import org.asteriskjava.manager.action.*;
import org.asteriskjava.manager.response.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
public class CallManager {
private static final Logger logger = LoggerFactory.getLogger(CallManager.class);
private final AsteriskManager asteriskManager;
public CallManager(AsteriskManager asteriskManager) {
this.asteriskManager = asteriskManager;
}
public ManagerResponse originateCall(String callerId, String extension, String context) {
OriginateAction originateAction = new OriginateAction();
originateAction.setChannel("SIP/" + extension);
originateAction.setContext(context);
originateAction.setExten(extension);
originateAction.setPriority(1);
originateAction.setCallerId(callerId);
originateAction.setTimeout(30000L); // 30 seconds
logger.info("Originating call to {} with caller ID {}", extension, callerId);
return asteriskManager.executeAction(originateAction);
}
public ManagerResponse originateCallWithApplication(String channel, String application, String data) {
OriginateAction originateAction = new OriginateAction();
originateAction.setChannel(channel);
originateAction.setApplication(application);
originateAction.setData(data);
originateAction.setTimeout(30000L);
return asteriskManager.executeAction(originateAction);
}
public ManagerResponse redirectCall(String channel, String context, String extension, int priority) {
RedirectAction redirectAction = new RedirectAction();
redirectAction.setChannel(channel);
redirectAction.setContext(context);
redirectAction.setExten(extension);
redirectAction.setPriority(priority);
logger.info("Redirecting call on channel {} to {}/{}", channel, context, extension);
return asteriskManager.executeAction(redirectAction);
}
public ManagerResponse hangupCall(String channel) {
HangupAction hangupAction = new HangupAction();
hangupAction.setChannel(channel);
logger.info("Hanging up call on channel: {}", channel);
return asteriskManager.executeAction(hangupAction);
}
public ManagerResponse hangupCallWithCause(String channel, int cause) {
HangupAction hangupAction = new HangupAction();
hangupAction.setChannel(channel);
hangupAction.setCause(cause);
logger.info("Hanging up call on channel {} with cause: {}", channel, cause);
return asteriskManager.executeAction(hangupAction);
}
public ManagerResponse bridgeCalls(String channel1, String channel2) {
BridgeAction bridgeAction = new BridgeAction();
bridgeAction.setChannel1(channel1);
bridgeAction.setChannel2(channel2);
logger.info("Bridging channels {} and {}", channel1, channel2);
return asteriskManager.executeAction(bridgeAction);
}
public ManagerResponse setCallerId(String channel, String callerId) {
SetVarAction setVarAction = new SetVarAction();
setVarAction.setChannel(channel);
setVarAction.setVariable("CALLERID(num)");
setVarAction.setValue(callerId);
logger.info("Setting caller ID for channel {} to {}", channel, callerId);
return asteriskManager.executeAction(setVarAction);
}
public ManagerResponse playDTMF(String channel, String digits) {
PlayDtmfAction playDtmfAction = new PlayDtmfAction();
playDtmfAction.setChannel(channel);
playDtmfAction.setDigit(digits);
logger.info("Playing DTMF digits {} on channel {}", digits, channel);
return asteriskManager.executeAction(playDtmfAction);
}
public ManagerResponse monitorCall(String channel, String filename, String format, boolean mix) {
MonitorAction monitorAction = new MonitorAction();
monitorAction.setChannel(channel);
monitorAction.setFile(filename);
monitorAction.setFormat(format);
monitorAction.setMix(mix);
logger.info("Starting call monitoring for channel {} to file {}", channel, filename);
return asteriskManager.executeAction(monitorAction);
}
public ManagerResponse stopMonitor(String channel) {
StopMonitorAction stopMonitorAction = new StopMonitorAction();
stopMonitorAction.setChannel(channel);
logger.info("Stopping call monitoring for channel {}", channel);
return asteriskManager.executeAction(stopMonitorAction);
}
public ManagerResponse pauseMonitor(String channel) {
PauseMonitorAction pauseMonitorAction = new PauseMonitorAction();
pauseMonitorAction.setChannel(channel);
logger.info("Pausing call monitoring for channel {}", channel);
return asteriskManager.executeAction(pauseMonitorAction);
}
public ManagerResponse unpauseMonitor(String channel) {
UnpauseMonitorAction unpauseMonitorAction = new UnpauseMonitorAction();
unpauseMonitorAction.setChannel(channel);
logger.info("Unpausing call monitoring for channel {}", channel);
return asteriskManager.executeAction(unpauseMonitorAction);
}
// Advanced call control with error handling
public CompletableFuture<Boolean> originateCallWithRetry(String callerId, String extension, 
String context, int maxRetries) {
return CompletableFuture.supplyAsync(() -> {
int retries = 0;
while (retries < maxRetries) {
try {
ManagerResponse response = originateCall(callerId, extension, context);
if (response.getResponse().equals("Success")) {
logger.info("Call originated successfully to {}", extension);
return true;
}
} catch (Exception e) {
logger.warn("Call origination attempt {} failed: {}", retries + 1, e.getMessage());
}
retries++;
if (retries < maxRetries) {
try {
Thread.sleep(2000); // Wait 2 seconds before retry
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
return false;
}
}
}
logger.error("Failed to originate call to {} after {} attempts", extension, maxRetries);
return false;
});
}
// Get active channels
public Map<String, String> getActiveChannels() {
Map<String, String> activeChannels = new HashMap<>();
try {
StatusAction statusAction = new StatusAction();
ManagerResponse response = asteriskManager.executeAction(statusAction);
if (response instanceof ManagerResponse) {
// Parse status response to get active channels
// This is a simplified implementation
logger.debug("Retrieved status information");
}
} catch (Exception e) {
logger.error("Failed to get active channels", e);
}
return activeChannels;
}
}

IVR (Interactive Voice Response) System

Dynamic IVR Implementation

package com.asterisk.ivr;
import org.asteriskjava.manager.action.*;
import org.asteriskjava.manager.response.*;
import org.asteriskjava.manager.event.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class IVRManager {
private static final Logger logger = LoggerFactory.getLogger(IVRManager.class);
private final AsteriskManager asteriskManager;
private final Map<String, IVRSession> activeSessions;
private final ScheduledExecutorService scheduler;
public IVRManager(AsteriskManager asteriskManager) {
this.asteriskManager = asteriskManager;
this.activeSessions = new ConcurrentHashMap<>();
this.scheduler = Executors.newScheduledThreadPool(5);
setupEventHandlers();
}
public static class IVRSession {
private final String channel;
private final String sessionId;
private IVRNode currentNode;
private Map<String, Object> sessionData;
private long lastActivity;
private int timeoutSeconds;
public IVRSession(String channel, String sessionId, IVRNode startNode) {
this.channel = channel;
this.sessionId = sessionId;
this.currentNode = startNode;
this.sessionData = new HashMap<>();
this.lastActivity = System.currentTimeMillis();
this.timeoutSeconds = 300; // 5 minutes default timeout
}
// Getters and setters
public String getChannel() { return channel; }
public String getSessionId() { return sessionId; }
public IVRNode getCurrentNode() { return currentNode; }
public void setCurrentNode(IVRNode node) { this.currentNode = node; }
public Map<String, Object> getSessionData() { return sessionData; }
public long getLastActivity() { return lastActivity; }
public void updateActivity() { this.lastActivity = System.currentTimeMillis(); }
public boolean isExpired() { 
return (System.currentTimeMillis() - lastActivity) > (timeoutSeconds * 1000L); 
}
}
public static abstract class IVRNode {
protected final String id;
protected final String name;
public IVRNode(String id, String name) {
this.id = id;
this.name = name;
}
public abstract void enter(IVRSession session, AsteriskManager manager);
public abstract IVRNode handleInput(IVRSession session, String input);
// Getters
public String getId() { return id; }
public String getName() { return name; }
}
public static class PlaybackNode extends IVRNode {
private final String soundFile;
private final IVRNode nextNode;
private final int timeout;
public PlaybackNode(String id, String name, String soundFile, IVRNode nextNode, int timeout) {
super(id, name);
this.soundFile = soundFile;
this.nextNode = nextNode;
this.timeout = timeout;
}
@Override
public void enter(IVRSession session, AsteriskManager manager) {
try {
// Play sound file
PlayDtmfAction playAction = new PlayDtmfAction();
playAction.setChannel(session.getChannel());
// Note: For actual sound playback, you'd use different approach
// This is simplified for demonstration
logger.info("Playing sound {} on channel {}", soundFile, session.getChannel());
// Wait for input
session.updateActivity();
} catch (Exception e) {
logger.error("Error in playback node {}", id, e);
}
}
@Override
public IVRNode handleInput(IVRSession session, String input) {
session.updateActivity();
return nextNode;
}
}
public static class MenuNode extends IVRNode {
private final String prompt;
private final Map<String, IVRNode> options;
private final IVRNode timeoutNode;
private final IVRNode invalidNode;
private final int maxDigits;
public MenuNode(String id, String name, String prompt, int maxDigits, 
IVRNode timeoutNode, IVRNode invalidNode) {
super(id, name);
this.prompt = prompt;
this.options = new HashMap<>();
this.timeoutNode = timeoutNode;
this.invalidNode = invalidNode;
this.maxDigits = maxDigits;
}
public void addOption(String digit, IVRNode node) {
options.put(digit, node);
}
@Override
public void enter(IVRSession session, AsteriskManager manager) {
try {
// Play menu prompt
logger.info("Playing menu prompt: {} on channel {}", prompt, session.getChannel());
// Wait for digit input
session.updateActivity();
} catch (Exception e) {
logger.error("Error in menu node {}", id, e);
}
}
@Override
public IVRNode handleInput(IVRSession session, String input) {
session.updateActivity();
if (input == null || input.trim().isEmpty()) {
return timeoutNode;
}
IVRNode selectedNode = options.get(input);
if (selectedNode != null) {
logger.info("Menu selection: {} -> {}", input, selectedNode.getName());
return selectedNode;
} else {
logger.warn("Invalid menu selection: {}", input);
return invalidNode;
}
}
}
public void startIVRSession(String channel, IVRNode startNode) {
String sessionId = UUID.randomUUID().toString();
IVRSession session = new IVRSession(channel, sessionId, startNode);
activeSessions.put(channel, session);
logger.info("Starting IVR session {} for channel {}", sessionId, channel);
// Enter the starting node
enterNode(session, startNode);
// Schedule session timeout check
scheduler.scheduleAtFixedRate(() -> checkSessionTimeouts(), 1, 1, TimeUnit.MINUTES);
}
private void enterNode(IVRSession session, IVRNode node) {
try {
session.setCurrentNode(node);
node.enter(session, asteriskManager);
} catch (Exception e) {
logger.error("Error entering IVR node {}", node.getId(), e);
endIVRSession(session.getChannel());
}
}
public void handleDTMFInput(String channel, String digit) {
IVRSession session = activeSessions.get(channel);
if (session == null) {
logger.warn("No active IVR session for channel: {}", channel);
return;
}
try {
IVRNode currentNode = session.getCurrentNode();
IVRNode nextNode = currentNode.handleInput(session, digit);
if (nextNode != null) {
enterNode(session, nextNode);
} else {
logger.info("IVR session completed for channel: {}", channel);
endIVRSession(channel);
}
} catch (Exception e) {
logger.error("Error handling DTMF input for channel: {}", channel, e);
endIVRSession(channel);
}
}
public void endIVRSession(String channel) {
IVRSession session = activeSessions.remove(channel);
if (session != null) {
logger.info("Ending IVR session {} for channel {}", session.getSessionId(), channel);
// Clean up resources
// Hang up or transfer call as needed
}
}
private void checkSessionTimeouts() {
Iterator<Map.Entry<String, IVRSession>> iterator = activeSessions.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, IVRSession> entry = iterator.next();
IVRSession session = entry.getValue();
if (session.isExpired()) {
logger.warn("IVR session timeout for channel: {}", session.getChannel());
iterator.remove();
// Handle timeout (play message, transfer, etc.)
handleSessionTimeout(session);
}
}
}
private void handleSessionTimeout(IVRSession session) {
try {
// Play timeout message
logger.info("Handling timeout for session: {}", session.getSessionId());
// Hang up or transfer call
asteriskManager.executeAction(new HangupAction(session.getChannel()));
} catch (Exception e) {
logger.error("Error handling session timeout", e);
}
}
private void setupEventHandlers() {
asteriskManager.addEventListener(new ManagerEventListener() {
@Override
public void onManagerEvent(ManagerEvent event) {
if (event instanceof DtmfEvent) {
handleDtmfEvent((DtmfEvent) event);
} else if (event instanceof HangupEvent) {
handleHangupEvent((HangupEvent) event);
}
}
});
}
private void handleDtmfEvent(DtmfEvent event) {
String channel = event.getChannel();
String digit = event.getDigit();
logger.debug("DTMF event - Channel: {}, Digit: {}", channel, digit);
handleDTMFInput(channel, digit);
}
private void handleHangupEvent(HangupEvent event) {
String channel = event.getChannel();
endIVRSession(channel);
}
// Factory method for common IVR flows
public static IVRNode createMainMenuIVR() {
// Welcome node
PlaybackNode welcomeNode = new PlaybackNode("welcome", "Welcome", 
"welcome", null, 5);
// Main menu
MenuNode mainMenu = new MenuNode("main_menu", "Main Menu", 
"main-menu-prompt", 1, null, null);
// Option nodes
PlaybackNode option1Node = new PlaybackNode("option1", "Option 1", 
"option1-info", mainMenu, 5);
PlaybackNode option2Node = new PlaybackNode("option2", "Option 2", 
"option2-info", mainMenu, 5);
PlaybackNode operatorNode = new PlaybackNode("operator", "Operator", 
"connecting-operator", null, 10);
// Configure menu options
mainMenu.addOption("1", option1Node);
mainMenu.addOption("2", option2Node);
mainMenu.addOption("0", operatorNode);
// Set welcome node's next node
welcomeNode = new PlaybackNode("welcome", "Welcome", 
"welcome", mainMenu, 5);
return welcomeNode;
}
}

Real-Time Event Handling

Advanced Event Management

package com.asterisk.events;
import org.asteriskjava.manager.event.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
import java.util.concurrent.*;
import java.util.function.Consumer;
public class EventManager {
private static final Logger logger = LoggerFactory.getLogger(EventManager.class);
private final AsteriskManager asteriskManager;
private final Map<Class<? extends ManagerEvent>, List<Consumer<ManagerEvent>>> eventHandlers;
private final ExecutorService eventExecutor;
private final BlockingQueue<ManagerEvent> eventQueue;
private volatile boolean processingEvents = false;
public EventManager(AsteriskManager asteriskManager) {
this.asteriskManager = asteriskManager;
this.eventHandlers = new ConcurrentHashMap<>();
this.eventExecutor = Executors.newFixedThreadPool(10);
this.eventQueue = new LinkedBlockingQueue<>(1000);
startEventProcessing();
registerDefaultEventHandlers();
}
public <T extends ManagerEvent> void registerEventHandler(Class<T> eventType, Consumer<T> handler) {
eventHandlers.computeIfAbsent(eventType, k -> new CopyOnWriteArrayList<>())
.add((Consumer<ManagerEvent>) handler);
logger.debug("Registered handler for event type: {}", eventType.getSimpleName());
}
public void unregisterEventHandler(Class<? extends ManagerEvent> eventType, Consumer<?> handler) {
List<Consumer<ManagerEvent>> handlers = eventHandlers.get(eventType);
if (handlers != null) {
handlers.remove(handler);
logger.debug("Unregistered handler for event type: {}", eventType.getSimpleName());
}
}
public void registerChannelEventHandler(Consumer<ChannelEvent> handler) {
registerEventHandler(ChannelEvent.class, handler);
}
public void registerCallEventHandler(Consumer<AbstractChannelEvent> handler) {
registerEventHandler(AbstractChannelEvent.class, handler);
}
public void registerDialEventHandler(Consumer<DialEvent> handler) {
registerEventHandler(DialEvent.class, handler);
}
public void registerHangupEventHandler(Consumer<HangupEvent> handler) {
registerEventHandler(HangupEvent.class, handler);
}
private void startEventProcessing() {
processingEvents = true;
Thread processorThread = new Thread(() -> {
while (processingEvents) {
try {
ManagerEvent event = eventQueue.poll(1, TimeUnit.SECONDS);
if (event != null) {
processEvent(event);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
} catch (Exception e) {
logger.error("Error processing event", e);
}
}
}, "Asterisk-Event-Processor");
processorThread.setDaemon(true);
processorThread.start();
// Register with Asterisk manager
asteriskManager.addEventListener(this::queueEvent);
}
private void queueEvent(ManagerEvent event) {
if (!eventQueue.offer(event)) {
logger.warn("Event queue full, dropping event: {}", event.getClass().getSimpleName());
}
}
private void processEvent(ManagerEvent event) {
Class<? extends ManagerEvent> eventType = event.getClass();
// Get handlers for this specific event type
List<Consumer<ManagerEvent>> specificHandlers = eventHandlers.get(eventType);
if (specificHandlers != null) {
specificHandlers.forEach(handler -> {
eventExecutor.submit(() -> {
try {
handler.accept(event);
} catch (Exception e) {
logger.error("Error in event handler for {}", eventType.getSimpleName(), e);
}
});
});
}
// Get handlers for superclass/interface types
eventHandlers.entrySet().stream()
.filter(entry -> entry.getKey().isInstance(event))
.flatMap(entry -> entry.getValue().stream())
.forEach(handler -> {
eventExecutor.submit(() -> {
try {
handler.accept(event);
} catch (Exception e) {
logger.error("Error in event handler", e);
}
});
});
}
private void registerDefaultEventHandlers() {
// Default hangup handler
registerEventHandler(HangupEvent.class, event -> {
HangupEvent hangupEvent = (HangupEvent) event;
logger.info("Call hung up - Channel: {}, UniqueId: {}, Cause: {}", 
hangupEvent.getChannel(), hangupEvent.getUniqueId(), hangupEvent.getCause());
});
// Default new channel handler
registerEventHandler(NewChannelEvent.class, event -> {
NewChannelEvent newChannelEvent = (NewChannelEvent) event;
logger.info("New channel created - Channel: {}, State: {}, CallerId: {}", 
newChannelEvent.getChannel(), newChannelEvent.getChannelState(), 
newChannelEvent.getCallerIdNum());
});
// Default dial handler
registerEventHandler(DialEvent.class, event -> {
DialEvent dialEvent = (DialEvent) event;
logger.info("Dial event - Source: {}, Destination: {}, Status: {}", 
dialEvent.getSrc(), dialEvent.getDestination(), dialEvent.getDialStatus());
});
}
public void stop() {
processingEvents = false;
eventExecutor.shutdown();
try {
if (!eventExecutor.awaitTermination(10, TimeUnit.SECONDS)) {
eventExecutor.shutdownNow();
}
} catch (InterruptedException e) {
eventExecutor.shutdownNow();
Thread.currentThread().interrupt();
}
}
// Utility method to wait for specific event
public <T extends ManagerEvent> CompletableFuture<T> waitForEvent(Class<T> eventType, 
Predicate<T> condition, 
long timeout, TimeUnit unit) {
CompletableFuture<T> future = new CompletableFuture<>();
Consumer<ManagerEvent> handler = new Consumer<ManagerEvent>() {
@Override
public void accept(ManagerEvent event) {
if (eventType.isInstance(event) && condition.test((T) event)) {
future.complete((T) event);
unregisterEventHandler(eventType, this);
}
}
};
registerEventHandler(eventType, handler);
// Schedule timeout
scheduler.schedule(() -> {
if (!future.isDone()) {
future.completeExceptionally(new TimeoutException("Event wait timed out"));
unregisterEventHandler(eventType, handler);
}
}, timeout, unit);
return future;
}
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
}

AGI (Asterisk Gateway Interface) Integration

FastAGI Server Implementation

package com.asterisk.agi;
import org.asteriskjava.fastagi.*;
import org.asteriskjava.fastagi.command.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.InetAddress;
import java.util.Map;
public class FastAGIServer {
private static final Logger logger = LoggerFactory.getLogger(FastAGIServer.class);
private final AgiServer agiServer;
private final int port;
public FastAGIServer(int port) {
this.port = port;
this.agiServer = new DefaultAgiServer();
setupAgiMapping();
}
public void start() {
try {
agiServer.setPort(port);
agiServer.setBindAddress(InetAddress.getByName("0.0.0.0"));
agiServer.startup();
logger.info("FastAGI server started on port {}", port);
} catch (Exception e) {
logger.error("Failed to start FastAGI server", e);
throw new RuntimeException("FastAGI server startup failed", e);
}
}
public void stop() {
try {
agiServer.shutdown();
logger.info("FastAGI server stopped");
} catch (Exception e) {
logger.error("Error stopping FastAGI server", e);
}
}
private void setupAgiMapping() {
agiServer.setMapping(new ResourceBundleMapping(
"com.asterisk.agi.agi-mapping",
"fastagi-mapping"
));
}
// Custom AGI script implementation
public static class IVRAGIScript extends BaseAgiScript {
private static final Logger logger = LoggerFactory.getLogger(IVRAGIScript.class);
@Override
public void service(AgiRequest request, AgiChannel channel) throws AgiException {
try {
logger.info("AGI Script started - Channel: {}, UniqueId: {}", 
request.getChannel(), request.getUniqueId());
// Get AGI parameters
Map<String, String> parameters = request.getParameters();
String scriptName = request.getScript();
// Answer the call
answer();
// Play welcome message
streamFile("welcome");
// Main menu loop
boolean continueProcessing = true;
while (continueProcessing) {
// Play menu options
streamFile("main-menu");
// Wait for digit input
String digit = getData("beep", 10000, 1);
if (digit == null || digit.isEmpty()) {
// Timeout
streamFile("goodbye");
continueProcessing = false;
} else {
continueProcessing = handleMenuSelection(digit);
}
}
// Hang up
hangup();
} catch (Exception e) {
logger.error("Error in AGI script", e);
try {
hangup();
} catch (AgiException ex) {
// Ignore
}
}
}
private boolean handleMenuSelection(String digit) throws AgiException {
switch (digit) {
case "1":
streamFile("option1-info");
return true;
case "2":
streamFile("option2-info");
return true;
case "0":
streamFile("connecting-operator");
// Transfer to operator
setExtension("operator");
setPriority("1");
return false;
case "9":
// Repeat menu
return true;
case "*":
// Exit
streamFile("goodbye");
return false;
default:
streamFile("invalid-option");
return true;
}
}
}
public static class CallbackAGIScript extends BaseAgiScript {
private final CallbackHandler callbackHandler;
public CallbackAGIScript(CallbackHandler callbackHandler) {
this.callbackHandler = callbackHandler;
}
@Override
public void service(AgiRequest request, AgiChannel channel) throws AgiException {
try {
callbackHandler.handleAgiRequest(request, channel, this);
} catch (Exception e) {
logger.error("Error in callback AGI script", e);
throw new AgiException("Callback handler error", e);
}
}
}
public interface CallbackHandler {
void handleAgiRequest(AgiRequest request, AgiChannel channel, AgiScript script) throws AgiException;
}
// Database-driven AGI script
public static class DatabaseAGIScript extends BaseAgiScript {
@Override
public void service(AgiRequest request, AgiChannel channel) throws AgiException {
try {
// Answer call
answer();
// Get caller ID
String callerId = request.getCallerIdNumber();
// Look up customer in database
CustomerInfo customer = lookupCustomer(callerId);
if (customer != null) {
// Personalized greeting
streamFile("welcome-known-customer");
sayDigits(customer.getAccountNumber());
streamFile("your-balance-is");
sayNumber(customer.getBalance());
} else {
// Generic greeting
streamFile("welcome-new-customer");
}
// Continue with IVR...
} catch (Exception e) {
logger.error("Error in database AGI script", e);
}
}
private CustomerInfo lookupCustomer(String callerId) {
// Database lookup implementation
// This is a placeholder
return null;
}
}
public static class CustomerInfo {
private String accountNumber;
private double balance;
private String customerName;
// Getters and setters
public String getAccountNumber() { return accountNumber; }
public void setAccountNumber(String accountNumber) { this.accountNumber = accountNumber; }
public double getBalance() { return balance; }
public void setBalance(double balance) { this.balance = balance; }
public String getCustomerName() { return customerName; }
public void setCustomerName(String customerName) { this.customerName = customerName; }
}
}

Spring Boot Integration

Spring Configuration and Services

package com.asterisk.config;
import org.asteriskjava.manager.ManagerConnection;
import org.asteriskjava.manager.ManagerConnectionFactory;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
@Configuration
@EnableAsync
@EnableScheduling
@ConfigurationProperties(prefix = "asterisk")
public class AsteriskConfig {
private String host;
private int port;
private String username;
private String password;
private int agiPort;
private boolean enabled;
// Getters and setters
public String getHost() { return host; }
public void setHost(String host) { this.host = host; }
public int getPort() { return port; }
public void setPort(int port) { this.port = port; }
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
public int getAgiPort() { return agiPort; }
public void setAgiPort(int agiPort) { this.agiPort = agiPort; }
public boolean isEnabled() { return enabled; }
public void setEnabled(boolean enabled) { this.enabled = enabled; }
@Bean
public ManagerConnection managerConnection() {
ManagerConnectionFactory factory = new ManagerConnectionFactory(
host, port, username, password
);
return factory.createManagerConnection();
}
@Bean
public AsteriskManager asteriskManager(ManagerConnection managerConnection) {
return new AsteriskManager(host, port, username, password);
}
@Bean(initMethod = "start", destroyMethod = "stop")
public FastAGIServer fastAGIServer() {
return new FastAGIServer(agiPort);
}
@Bean
public CallManager callManager(AsteriskManager asteriskManager) {
return new CallManager(asteriskManager);
}
@Bean
public IVRManager ivrManager(AsteriskManager asteriskManager) {
return new IVRManager(asteriskManager);
}
@Bean
public EventManager eventManager(AsteriskManager asteriskManager) {
return new EventManager(asteriskManager);
}
}

Spring Service Implementation

package com.asterisk.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Service
public class AsteriskMonitoringService {
private static final Logger logger = LoggerFactory.getLogger(AsteriskMonitoringService.class);
@Autowired
private AsteriskManager asteriskManager;
@Autowired
private CallManager callManager;
@Autowired
private EventManager eventManager;
@Scheduled(fixedRate = 30000) // Every 30 seconds
public void healthCheck() {
if (!asteriskManager.healthCheck()) {
logger.error("Asterisk health check failed - attempting reconnect");
// Implement reconnection logic
}
}
@Scheduled(fixedRate = 60000) // Every minute
public void logSystemStatus() {
try {
// Log active calls count, system load, etc.
logger.info("Asterisk system status: {}", getSystemStatus());
} catch (Exception e) {
logger.error("Error logging system status", e);
}
}
private String getSystemStatus() {
// Implementation to get system status
return "Operational"; // Simplified
}
}

Complete Usage Example

Demo Application

package com.asterisk.demo;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.beans.factory.annotation.Autowired;
@SpringBootApplication
public class AsteriskDemoApplication implements CommandLineRunner {
@Autowired
private AsteriskManager asteriskManager;
@Autowired
private CallManager callManager;
@Autowired
private IVRManager ivrManager;
@Autowired
private EventManager eventManager;
public static void main(String[] args) {
SpringApplication.run(AsteriskDemoApplication.class, args);
}
@Override
public void run(String... args) throws Exception {
// Connect to Asterisk
asteriskManager.connect();
// Setup event handlers
setupEventHandlers();
// Demo: Make a test call
if (args.length > 0 && "demo".equals(args[0])) {
runDemo();
}
logger.info("Asterisk integration demo started successfully");
}
private void setupEventHandlers() {
// Register for call events
eventManager.registerCallEventHandler(event -> {
logger.info("Call event: {} - Channel: {}", 
event.getClass().getSimpleName(), event.getChannel());
});
// Register for specific dial events
eventManager.registerDialEventHandler(event -> {
if ("ANSWER".equals(event.getDialStatus())) {
logger.info("Call answered: {} -> {}", event.getSrc(), event.getDestination());
// Start IVR for answered calls
IVRManager.IVRNode mainMenu = IVRManager.createMainMenuIVR();
ivrManager.startIVRSession(event.getDestination(), mainMenu);
}
});
}
private void runDemo() throws Exception {
logger.info("Running Asterisk integration demo...");
// Wait for connection to stabilize
Thread.sleep(2000);
// Make a test call
ManagerResponse response = callManager.originateCall(
"DemoApp", "1001", "default"
);
if (response.getResponse().equals("Success")) {
logger.info("Demo call originated successfully");
} else {
logger.warn("Demo call origination failed: {}", response.getMessage());
}
}
}

Key Features Covered

  1. AMI Connection Management: Robust connection handling with health checks
  2. Call Control: Originate, redirect, hangup, and bridge calls
  3. IVR Systems: Dynamic menu-driven voice response systems
  4. Event Handling: Real-time event processing with queuing
  5. AGI Integration: FastAGI server for advanced call control
  6. Spring Boot Integration: Production-ready configuration
  7. Error Handling: Comprehensive exception management
  8. Monitoring: Health checks and system status monitoring

This comprehensive Asterisk Java integration provides a solid foundation for building enterprise-grade telephony applications with robust error handling, real-time event processing, and scalable architecture.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper