org.asteriskjava.pbx.internal.asterisk.MeetmeRoomControl Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of asterisk-java Show documentation
Show all versions of asterisk-java Show documentation
The free Java library for Asterisk PBX integration.
package org.asteriskjava.pbx.internal.asterisk;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.atomic.AtomicReference;
import org.asteriskjava.AsteriskVersion;
import org.asteriskjava.live.ManagerCommunicationException;
import org.asteriskjava.pbx.AsteriskSettings;
import org.asteriskjava.pbx.Channel;
import org.asteriskjava.pbx.ListenerPriority;
import org.asteriskjava.pbx.PBX;
import org.asteriskjava.pbx.PBXException;
import org.asteriskjava.pbx.PBXFactory;
import org.asteriskjava.pbx.asterisk.wrap.actions.CommandAction;
import org.asteriskjava.pbx.asterisk.wrap.actions.ConfbridgeListAction;
import org.asteriskjava.pbx.asterisk.wrap.events.ConfbridgeListEvent;
import org.asteriskjava.pbx.asterisk.wrap.events.ManagerEvent;
import org.asteriskjava.pbx.asterisk.wrap.events.MeetMeJoinEvent;
import org.asteriskjava.pbx.asterisk.wrap.events.MeetMeLeaveEvent;
import org.asteriskjava.pbx.asterisk.wrap.events.ResponseEvent;
import org.asteriskjava.pbx.asterisk.wrap.events.ResponseEvents;
import org.asteriskjava.pbx.asterisk.wrap.response.CommandResponse;
import org.asteriskjava.pbx.asterisk.wrap.response.ManagerResponse;
import org.asteriskjava.pbx.internal.core.AsteriskPBX;
import org.asteriskjava.pbx.internal.core.CoherentManagerEventListener;
import org.asteriskjava.pbx.internal.managerAPI.EventListenerBaseClass;
import org.asteriskjava.util.Log;
import org.asteriskjava.util.LogFactory;
public class MeetmeRoomControl extends EventListenerBaseClass implements CoherentManagerEventListener
{
/*
* listens for a channel entering or leaving meetme rooms. when there is
* only 1 channel left in a room it sets it as inactive .It will not set to
* inactive unless okToKill is set to true. It is set to false by
* default.Also manages the available room list.
*/
private static final Log logger = LogFactory.getLog(MeetmeRoomControl.class);
private Integer meetmeBaseAddress;
private MeetmeRoom rooms[];
private int roomCount;
private boolean meetmeInstalled = false;
private final static AtomicReference self = new AtomicReference<>();
synchronized public static void init(PBX pbx, final int roomCount) throws NoMeetmeException
{
if (MeetmeRoomControl.self.get() != null)
{
logger.warn("The MeetmeRoomControl has already been initialised."); //$NON-NLS-1$
}
else
{
MeetmeRoomControl.self.set(new MeetmeRoomControl(pbx, roomCount));
}
}
public static MeetmeRoomControl getInstance()
{
if (MeetmeRoomControl.self.get() == null)
{
throw new IllegalStateException(
"The MeetmeRoomControl has not been initialised. Please call MeetmeRoomControl.init()."); //$NON-NLS-1$
}
return MeetmeRoomControl.self.get();
}
private MeetmeRoomControl(PBX pbx, final int roomCount) throws NoMeetmeException
{
super("MeetmeRoomControl", pbx); //$NON-NLS-1$
this.roomCount = roomCount;
final AsteriskSettings settings = PBXFactory.getActiveProfile();
this.meetmeBaseAddress = settings.getMeetmeBaseAddress();
this.rooms = new MeetmeRoom[roomCount];
this.configure((AsteriskPBX) pbx);
this.startListener();
}
@Override
public HashSet> requiredEvents()
{
HashSet> required = new HashSet<>();
required.add(MeetMeJoinEvent.class);
required.add(MeetMeLeaveEvent.class);
return required;
}
/*
* returns the next available meetme room, or null if no rooms are
* available.
*/
public synchronized MeetmeRoom findAvailableRoom(RoomOwner newOwner)
{
int count = 0;
for (final MeetmeRoom room : this.rooms)
{
if (MeetmeRoomControl.logger.isDebugEnabled())
{
MeetmeRoomControl.logger.debug("room " + room.getRoomNumber() + " count " + count);
}
if (room.getOwner() == null || !room.getOwner().isRoomStillRequired())
{
/*
* new code to attempt to recover uncleared meetme rooms safely
*/
try
{
final Date lastUpdated = room.getLastUpdated();
final long now = new Date().getTime();
if (lastUpdated != null)
{
final long elapsedTime = now - lastUpdated.getTime();
MeetmeRoomControl.logger
.error("room: " + room.getRoomNumber() + " count: " + count + " elapsed: " + elapsedTime);
if ((elapsedTime > 1800000) && (room.getChannelCount() < 2))
{
MeetmeRoomControl.logger.error("clearing room"); //$NON-NLS-1$
room.setInactive();
}
}
}
catch (final Exception e)
{
/*
* attempt to make this new change safe
*/
MeetmeRoomControl.logger.error(e, e);
}
if (room.getChannelCount() == 0)
{
room.setInactive();
room.setOwner(newOwner);
MeetmeRoomControl.logger.warn("Returning available room " + room.getRoomNumber());
return room;
}
}
else
{
logger.warn("Meetme " + room.getRoomNumber() + " is still in use by " + room.getOwner());
}
count++;
}
MeetmeRoomControl.logger.error("no more available rooms");
return null;
}
/**
* Returns the MeetmeRoom for the given room number. The room number will be
* an integer value offset from the meetme base address.
*
* @param roomNumber the meetme room number
* @return
*/
synchronized private MeetmeRoom findMeetmeRoom(final String roomNumber)
{
MeetmeRoom foundRoom = null;
for (final MeetmeRoom room : this.rooms)
{
if (room.getRoomNumber().compareToIgnoreCase(roomNumber) == 0)
{
foundRoom = room;
break;
}
}
return foundRoom;
}
synchronized MeetmeRoom getRoom(final int room)
{
return this.rooms[room];
}
@Override
public void onManagerEvent(final ManagerEvent event)
{
MeetmeRoom room;
if (event instanceof MeetMeJoinEvent)
{
final MeetMeJoinEvent evt = (MeetMeJoinEvent) event;
room = this.findMeetmeRoom(evt.getMeetMe());
final Channel channel = evt.getChannel();
if (room != null)
{
if (room.addChannel(channel))
{
MeetmeRoomControl.logger.debug(channel + " has joined the conference " //$NON-NLS-1$
+ room.getRoomNumber() + " channelCount " + (room.getChannelCount())); //$NON-NLS-1$
room.setLastUpdated();
}
}
}
if (event instanceof MeetMeLeaveEvent)
{
final MeetMeLeaveEvent evt = (MeetMeLeaveEvent) event;
room = this.findMeetmeRoom(evt.getMeetMe());
final Channel channel = evt.getChannel();
if (room != null)
{
// ignore local dummy channels// &&
// !channel.toUpperCase().startsWith("LOCAL/")) {
if (MeetmeRoomControl.logger.isDebugEnabled())
{
MeetmeRoomControl.logger.debug(channel + " has left the conference " //$NON-NLS-1$
+ room.getRoomNumber() + " channel count " + (room.getChannelCount())); //$NON-NLS-1$
}
room.removeChannel(channel);
room.setLastUpdated();
if (room.getChannelCount() < 2 && room.getForceClose())
{
this.hangupChannels(room);
room.setInactive();
}
if (room.getChannelCount() < 1)
{
room.setInactive();
}
}
}
}
public void hangupChannels(final MeetmeRoom room)
{
final Channel Channels[] = room.getChannels();
if (room.isActive())
{
PBX pbx = PBXFactory.getActivePBX();
for (final Channel channel : Channels)
{
room.removeChannel(channel);
try
{
logger.warn("Hanging up");
pbx.hangup(channel);
}
catch (IllegalArgumentException | IllegalStateException | PBXException e)
{
logger.error(e, e);
}
}
}
}
private void configure(AsteriskPBX pbx) throws NoMeetmeException
{
final int base = this.meetmeBaseAddress;
for (int r = 0; r < this.roomCount; r++)
{
this.rooms[r] = new MeetmeRoom(r + base);
}
try
{
String command;
if (pbx.getVersion().isAtLeast(AsteriskVersion.ASTERISK_13))
{
command = "ConfBridge list"; //$NON-NLS-1$
ConfbridgeListAction action = new ConfbridgeListAction();
final ResponseEvents response = pbx.sendEventGeneratingAction(action, 3000);
Map roomChannelCount = new HashMap<>();
for (ResponseEvent event : response.getEvents())
{
ConfbridgeListEvent e = (ConfbridgeListEvent) event;
Integer current = roomChannelCount.get(e.getConference());
if (current == null)
{
roomChannelCount.put(e.getConference(), 1);
}
else
{
roomChannelCount.put(e.getConference(), current + 1);
}
}
for (Entry entry : roomChannelCount.entrySet())
{
setRoomCount(entry.getKey(), entry.getValue(), Integer.parseInt(entry.getKey()));
}
this.meetmeInstalled = true;
}
else
{
if (pbx.getVersion().isAtLeast(AsteriskVersion.ASTERISK_1_6))
{
command = "meetme list"; //$NON-NLS-1$
}
else
{
command = "meetme"; //$NON-NLS-1$
}
final CommandAction commandAction = new CommandAction(command);
final ManagerResponse response = pbx.sendAction(commandAction, 3000);
if (!(response instanceof CommandResponse))
{
throw new ManagerCommunicationException(response.getMessage(), null);
}
final CommandResponse commandResponse = (CommandResponse) response;
MeetmeRoomControl.logger.debug("parsing active meetme rooms"); //$NON-NLS-1$
for (final String line : commandResponse.getResult())
{
this.parseMeetme(line);
this.meetmeInstalled = true;
MeetmeRoomControl.logger.debug(line);
}
}
}
catch (final NoMeetmeException e)
{
throw e;
}
catch (final Exception e)
{
MeetmeRoomControl.logger.error(e, e);
throw new NoMeetmeException(e.getLocalizedMessage());
}
}
private synchronized void parseMeetme(final String line) throws NoMeetmeException
{
if (line != null)
{
if (line.toLowerCase().startsWith("no such command 'meetme'")) //$NON-NLS-1$
{
throw new NoMeetmeException("Asterisk is not configured correctly! Please enable the MeetMe app"); //$NON-NLS-1$
}
if ((!line.toLowerCase().startsWith("no active meetme conferences.")) //$NON-NLS-1$
&& (!line.toLowerCase().startsWith("conf num")) //$NON-NLS-1$
&& (!line.toLowerCase().startsWith("* total number")) //$NON-NLS-1$
&& (!line.toLowerCase().startsWith("no such conference")) //$NON-NLS-1$
&& (!line.toLowerCase().startsWith("no such command 'meetme")) //$NON-NLS-1$
)
{
// Update the stats on each meetme
final String roomNumber = line.substring(0, 10).trim();
final String tmp = line.substring(11, 25).trim();
final int channelCount = Integer.parseInt(tmp);
final int roomNo = Integer.valueOf(roomNumber);
setRoomCount(roomNumber, channelCount, roomNo);
}
}
}
private void setRoomCount(final String roomNumber, final int channelCount, final int roomNo)
{
Integer base = this.meetmeBaseAddress;
// First check if its one of our rooms.
if ((roomNo >= base) && (roomNo < (base + this.roomCount)))
{
final MeetmeRoom room = this.findMeetmeRoom(roomNumber);
if (room != null)
{
if (room.getChannelCount() != channelCount)
{
/*
* After a restart there may have been meetme rooms left up
* and running with live calls. We need to identify any
* active rooms so we don't accidentally re-use an active
* room which would result in a crossed channel.
*/
MeetmeRoomControl.logger.warn("Room number: " + room.getRoomNumber() //$NON-NLS-1$
+ " has a server side channel count = " + channelCount //$NON-NLS-1$
+ " when the channel count for that room is: " + room.getChannelCount() //$NON-NLS-1$
+ " the server side channel count will be reset."); //$NON-NLS-1$
}
room.resetChannelCount(channelCount);
room.setActive();
}
// else "Found roomNumber:" + roomNumber + " but it was not
// in the list of rooms managed by MeetmeRoomControl.");
// //$NON-NLS-1$ //$NON-NLS-2$
}
}
public void stop()
{
this.close();
}
@Override
public ListenerPriority getPriority()
{
return ListenerPriority.NORMAL;
}
public boolean isMeetmeInstalled()
{
return this.meetmeInstalled;
}
}