/** * Manages all events related to agents on Asterisk server. For correct work ensure enabled * AgentCalledEvents. You have to set <code>eventwhencalled = yes</code> in <code>queues.conf</code> * . * * @author <a href="mailto:patrick.breucking{@nospam}gonicus.de">Patrick Breucking</a> * @version $Id$ * @since 0.3.1 */ public class AgentManager { private final Log logger = LogFactory.getLog(this.getClass()); private final AsteriskServerImpl server; /** A Map of agents by thier agentId. */ private final Map<String, AsteriskAgentImpl> agents; /** * A Map of agent in state RINGING by the caller id. Needed to return agent into idle state, if * call was not conneted. */ private final Map<String, AsteriskAgentImpl> ringingAgents; AgentManager(AsteriskServerImpl asteriskServerImpl) { this.server = asteriskServerImpl; agents = new HashMap<String, AsteriskAgentImpl>(); ringingAgents = new HashMap<String, AsteriskAgentImpl>(); } /** * Retrieves all agents registered at Asterisk server by sending an AgentsAction. * * @throws ManagerCommunicationException if communication with Asterisk server fails. */ void initialize() throws ManagerCommunicationException { ResponseEvents re; re = server.sendEventGeneratingAction(new AgentsAction()); for (ManagerEvent event : re.getEvents()) { if (event instanceof AgentsEvent) { logger.info(event); handleAgentsEvent((AgentsEvent) event); } } } void disconnected() { synchronized (agents) { agents.clear(); } } /** * On AgentsEvent create a new Agent. * * @param event generated by Asterisk server. */ void handleAgentsEvent(AgentsEvent event) { AsteriskAgentImpl agent = new AsteriskAgentImpl( server, event.getName(), "Agent/" + event.getAgent(), AgentState.valueOf(event.getStatus())); logger.info("Adding agent " + agent.getName() + "(" + agent.getAgentId() + ")"); addAgent(agent); } /** * Add a new agent to the manager. * * @param agent agent to add. */ private void addAgent(AsteriskAgentImpl agent) { synchronized (agents) { agents.put(agent.getAgentId(), agent); } server.fireNewAgent(agent); } /** * Return the requested agent. * * @param agentId identifier for agent * @return the requested agent */ AsteriskAgentImpl getAgentByAgentId(String agentId) { synchronized (agents) { return agents.get(agentId); } } /** * Update state if agent was called. * * @param event */ void handleAgentCalledEvent(AgentCalledEvent event) { AsteriskAgentImpl agent = getAgentByAgentId(event.getAgentCalled()); if (agent == null) { logger.error("Ignored AgentCalledEvent for unknown agent " + event.getAgentCalled()); return; } updateRingingAgents(event.getChannelCalling(), agent); updateAgentState(agent, AgentState.AGENT_RINGING); } /** * Set state of agent. * * @param agent */ private void updateAgentState(AsteriskAgentImpl agent, AgentState newState) { logger.info("Set state of agent " + agent.getAgentId() + " to " + newState); synchronized (agent) { agent.updateState(newState); } } /** * Updates state of agent, if the call in a queue was redirect to the next agent because the * ringed agent doesn't answer the call. After reset state, put the next agent in charge. * * @param channelCalling * @param agent */ private void updateRingingAgents(String channelCalling, AsteriskAgentImpl agent) { synchronized (ringingAgents) { if (ringingAgents.containsKey(channelCalling)) { updateAgentState(ringingAgents.get(channelCalling), AgentState.AGENT_IDLE); } ringingAgents.put(channelCalling, agent); } } /** * Update state if agent was connected to channel. * * @param event */ void handleAgentConnectEvent(AgentConnectEvent event) { AsteriskAgentImpl agent = getAgentByAgentId(event.getChannel()); if (agent == null) { logger.error("Ignored AgentConnectEvent for unknown agent " + event.getChannel()); return; } agent.updateState(AgentState.AGENT_ONCALL); } /** * Change state if agent logs in. * * @param event */ void handleAgentLoginEvent(AgentLoginEvent event) { AsteriskAgentImpl agent = getAgentByAgentId("Agent/" + event.getAgent()); if (agent == null) { synchronized (agents) { logger.error( "Ignored AgentLoginEvent for unknown agent " + event.getAgent() + ". Agents: " + agents.values().toString()); } return; } agent.updateState(AgentState.AGENT_IDLE); } /** * Change state if agent logs out. * * @param event */ void handleAgentLogoffEvent(AgentLogoffEvent event) { AsteriskAgentImpl agent = getAgentByAgentId("Agent/" + event.getAgent()); if (agent == null) { logger.error( "Ignored AgentLogoffEvent for unknown agent " + event.getAgent() + ". Agents: " + agents.values().toString()); return; } agent.updateState(AgentState.AGENT_LOGGEDOFF); } /** * Change state if agent logs in. * * @param event */ void handleAgentCallbackLoginEvent(AgentCallbackLoginEvent event) { AsteriskAgentImpl agent = getAgentByAgentId("Agent/" + event.getAgent()); if (agent == null) { synchronized (agents) { logger.error( "Ignored AgentCallbackLoginEvent for unknown agent " + event.getAgent() + ". Agents: " + agents.values().toString()); } return; } agent.updateState(AgentState.AGENT_IDLE); } /** * Change state if agent logs out. * * @param event */ void handleAgentCallbackLogoffEvent(AgentCallbackLogoffEvent event) { AsteriskAgentImpl agent = getAgentByAgentId("Agent/" + event.getAgent()); if (agent == null) { logger.error( "Ignored AgentCallbackLogoffEvent for unknown agent " + event.getAgent() + ". Agents: " + agents.values().toString()); return; } agent.updateState(AgentState.AGENT_LOGGEDOFF); } /** * Return all agents registered at Asterisk server. * * @return a collection of all agents. */ Collection<AsteriskAgent> getAgents() { Collection<AsteriskAgent> copy; synchronized (agents) { copy = new ArrayList<AsteriskAgent>(agents.values()); } return copy; } /** * Change state if connected call was terminated. * * @param event */ void handleAgentCompleteEvent(AgentCompleteEvent event) { AsteriskAgentImpl agent = getAgentByAgentId(event.getChannel()); if (agent == null) { logger.error("Ignored AgentCompleteEvent for unknown agent " + event.getChannel()); return; } agent.updateState(AgentState.AGENT_IDLE); } }
/** * Abstract base class for FastAGI and AsyncAGI servers. * * @since 1.0.0 */ public abstract class AbstractAgiServer { private final Log logger = LogFactory.getLog(getClass()); /** The default thread pool size. */ private static final int DEFAULT_POOL_SIZE = 10; /** The default thread pool size. */ private static final int DEFAULT_MAXIMUM_POOL_SIZE = 100; /** The minimum number of worker threads in the thread pool. */ private int poolSize = DEFAULT_POOL_SIZE; /** * The maximum number of worker threads in the thread pool. This equals the maximum number of * concurrent requests this AgiServer can serve. */ private int maximumPoolSize = DEFAULT_MAXIMUM_POOL_SIZE; /** The thread pool that contains the worker threads to process incoming requests. */ private ThreadPoolExecutor pool; /** The strategy to use for mapping AgiRequests to AgiScripts that serve them. */ private MappingStrategy mappingStrategy; /** The factory to use for creating new AgiChannel instances. */ private final AgiChannelFactory agiChannelFactory; private volatile boolean die = false; public AbstractAgiServer() { this(new DefaultAgiChannelFactory()); } /** * Creates a new AbstractAgiServer with the given channel factory. * * @param agiChannelFactory the AgiChannelFactory to use for creating new AgiChannel instances. * @since 1.0.0 */ public AbstractAgiServer(AgiChannelFactory agiChannelFactory) { if (agiChannelFactory == null) { throw new IllegalArgumentException("AgiChannelFactory must not be null"); } logger.debug("Using channelFactory " + agiChannelFactory.getClass().getCanonicalName()); this.agiChannelFactory = agiChannelFactory; } /** * Returns the AgiChannelFactory to use for creating new AgiChannel instances. * * @return the AgiChannelFactory to use for creating new AgiChannel instances. */ protected AgiChannelFactory getAgiChannelFactory() { return this.agiChannelFactory; } /** * Returns the default number of worker threads in the thread pool. * * @return the default number of worker threads in the thread pool. * @since 1.0.0 */ public synchronized int getPoolSize() { return poolSize; } /** * Sets the default number of worker threads in the thread pool. <br> * This is the number of threads that are available even if they are idle. <br> * The default pool size is 10. * * @param poolSize the size of the worker thread pool. * @throws IllegalArgumentException if the new pool size is negative * @see java.util.concurrent.ThreadPoolExecutor#setCorePoolSize(int) */ public synchronized void setPoolSize(int poolSize) { if (poolSize < 0) { throw new IllegalArgumentException("New poolSize (" + poolSize + ") is must not be negative"); } if (pool != null) { pool.setCorePoolSize(poolSize); } this.poolSize = poolSize; } /** * Returns the maximum number of worker threads in the thread pool. * * @return the maximum number of worker threads in the thread pool. * @since 1.0.0 */ public synchronized int getMaximumPoolSize() { return maximumPoolSize; } /** * Sets the maximum number of worker threads in the thread pool. <br> * This equals the maximum number of concurrent requests this AgiServer can serve. <br> * The default maximum pool size is 100. * * @param maximumPoolSize the maximum size of the worker thread pool. * @throws IllegalArgumentException if maximumPoolSize is less than current pool size or less than * or equal to 0. * @see java.util.concurrent.ThreadPoolExecutor#setMaximumPoolSize(int) */ public synchronized void setMaximumPoolSize(int maximumPoolSize) { if (maximumPoolSize <= 0) { throw new IllegalArgumentException( "New maximumPoolSize (" + maximumPoolSize + ") is must be positive"); } if (maximumPoolSize < poolSize) { throw new IllegalArgumentException( "New maximumPoolSize (" + maximumPoolSize + ") is less than current pool size (" + poolSize + ")"); } if (pool != null) { pool.setMaximumPoolSize(maximumPoolSize); } this.maximumPoolSize = maximumPoolSize; } /** * Sets the strategy to use for mapping AgiRequests to AgiScripts that serve them. * * @param mappingStrategy the mapping strategy to use. */ public void setMappingStrategy(MappingStrategy mappingStrategy) { this.mappingStrategy = mappingStrategy; } protected MappingStrategy getMappingStrategy() { return mappingStrategy; } protected boolean isDie() { return die; } protected synchronized void shutdown() { this.die = true; if (pool != null) { pool.shutdown(); } } @Override protected void finalize() throws Throwable { this.die = true; if (pool != null) { pool.shutdown(); } super.finalize(); } /** * Execute the runnable using the configured ThreadPoolExecutor obtained from {@link #getPool()}. * * @param command the command to run. * @throws RejectedExecutionException if the runnable can't be executed */ protected void execute(Runnable command) throws RejectedExecutionException { if (isDie()) { logger.warn("AgiServer is shutting down: Refused to execute AgiScript"); return; } getPool().execute(command); } protected void handleException(String message, Exception e) { logger.warn(message, e); } private synchronized ThreadPoolExecutor getPool() { if (pool == null) { pool = createPool(); logger.info("Thread pool started."); } return pool; } /** * Returns the approximate number of AgiConnectionHandler threads that are actively executing * tasks. * * @see ThreadPoolExecutor#getActiveCount() * @see #getPoolActiveThreadCount() * @see org.asteriskjava.fastagi.internal.AgiConnectionHandler#AGI_CONNECTION_HANDLERS */ public int getPoolActiveTaskCount() { if (pool != null) { return pool.getActiveCount(); } return -1; } // getPoolActiveCount public int getPoolActiveThreadCount() { if (pool != null) { return pool.getPoolSize(); } return -1; } // getPoolActiveThreadCount /** * Creates a new ThreadPoolExecutor to serve the AGI requests. The nature of this pool defines how * many concurrent requests can be handled. The default implementation returns a dynamic thread * pool defined by the poolSize and maximumPoolSize properties. * * <p>You can override this method to change this behavior. For example you can use a cached pool * with * * <pre> * return Executors.newCachedThreadPool(new DaemonThreadFactory()); * </pre> * * @return the ThreadPoolExecutor to use for serving AGI requests. * @see #setPoolSize(int) * @see #setMaximumPoolSize(int) */ protected ThreadPoolExecutor createPool() { return new ThreadPoolExecutor( poolSize, (maximumPoolSize < poolSize) ? poolSize : maximumPoolSize, 50000L, TimeUnit.MILLISECONDS, new SynchronousQueue<Runnable>(), new DaemonThreadFactory()); } }
/** * Manages channel events on behalf of an AsteriskServer. * * @author srt * @version $Id$ */ class ChannelManager { private final Log logger = LogFactory.getLog(getClass()); /** How long we wait before we remove hung up channels from memory (in milliseconds). */ private static final long REMOVAL_THRESHOLD = 15 * 60 * 1000L; // 15 minutes private static final long SLEEP_TIME_BEFORE_GET_VAR = 50L; private final AsteriskServerImpl server; /** A map of all active channel by their unique id. */ private final Set<AsteriskChannelImpl> channels; /** * Creates a new instance. * * @param server the server this channel manager belongs to. */ ChannelManager(AsteriskServerImpl server) { this.server = server; this.channels = new HashSet<AsteriskChannelImpl>(); } void initialize() throws ManagerCommunicationException { initialize(null); } void initialize(List<String> variables) throws ManagerCommunicationException { ResponseEvents re; disconnected(); StatusAction sa = new StatusAction(); sa.setVariables(variables); re = server.sendEventGeneratingAction(sa); for (ManagerEvent event : re.getEvents()) { if (event instanceof StatusEvent) { handleStatusEvent((StatusEvent) event); } } } void disconnected() { synchronized (channels) { channels.clear(); } } /** * Returns a collection of all active AsteriskChannels. * * @return a collection of all active AsteriskChannels. */ Collection<AsteriskChannel> getChannels() { Collection<AsteriskChannel> copy; synchronized (channels) { copy = new ArrayList<AsteriskChannel>(channels.size() + 2); for (AsteriskChannel channel : channels) { if (channel.getState() != ChannelState.HUNGUP) { copy.add(channel); } } } return copy; } private void addChannel(AsteriskChannelImpl channel) { synchronized (channels) { channels.add(channel); } } /** Removes channels that have been hung more than {@link #REMOVAL_THRESHOLD} milliseconds. */ private void removeOldChannels() { Iterator<AsteriskChannelImpl> i; synchronized (channels) { i = channels.iterator(); while (i.hasNext()) { final AsteriskChannel channel = i.next(); final Date dateOfRemoval = channel.getDateOfRemoval(); if (channel.getState() == ChannelState.HUNGUP && dateOfRemoval != null) { final long diff = DateUtil.getDate().getTime() - dateOfRemoval.getTime(); if (diff >= REMOVAL_THRESHOLD) { i.remove(); } } } } } private AsteriskChannelImpl addNewChannel( String uniqueId, String name, Date dateOfCreation, String callerIdNumber, String callerIdName, ChannelState state, String account) { final AsteriskChannelImpl channel; final String traceId; channel = new AsteriskChannelImpl(server, name, uniqueId, dateOfCreation); channel.setCallerId(new CallerId(callerIdName, callerIdNumber)); channel.setAccount(account); channel.stateChanged(dateOfCreation, state); logger.info("Adding channel " + channel.getName() + "(" + channel.getId() + ")"); if (SLEEP_TIME_BEFORE_GET_VAR > 0) { try { Thread.sleep(SLEEP_TIME_BEFORE_GET_VAR); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } traceId = getTraceId(channel); channel.setTraceId(traceId); addChannel(channel); if (traceId != null && (!name.toLowerCase(Locale.ENGLISH).startsWith("local/") || (name.endsWith(",1") || name.endsWith(";1")))) { final OriginateCallbackData callbackData; callbackData = server.getOriginateCallbackDataByTraceId(traceId); if (callbackData != null && callbackData.getChannel() == null) { callbackData.setChannel(channel); try { callbackData.getCallback().onDialing(channel); } catch (Throwable t) { logger.warn("Exception dispatching originate progress.", t); } } } server.fireNewAsteriskChannel(channel); return channel; } void handleStatusEvent(StatusEvent event) { AsteriskChannelImpl channel; final Extension extension; boolean isNew = false; Map<String, String> variables = event.getVariables(); channel = getChannelImplById(event.getUniqueId()); if (channel == null) { Date dateOfCreation; if (event.getSeconds() != null) { dateOfCreation = new Date(DateUtil.getDate().getTime() - (event.getSeconds() * 1000L)); } else { dateOfCreation = DateUtil.getDate(); } channel = new AsteriskChannelImpl(server, event.getChannel(), event.getUniqueId(), dateOfCreation); isNew = true; if (variables != null) { for (String variable : variables.keySet()) { channel.updateVariable(variable, variables.get(variable)); } } } if (event.getContext() == null && event.getExtension() == null && event.getPriority() == null) { extension = null; } else { extension = new Extension(event.getContext(), event.getExtension(), event.getPriority()); } synchronized (channel) { channel.setCallerId(new CallerId(event.getCallerIdName(), event.getCallerIdNum())); channel.setAccount(event.getAccountCode()); if (event.getChannelState() != null) { channel.stateChanged( event.getDateReceived(), ChannelState.valueOf(event.getChannelState())); } channel.extensionVisited(event.getDateReceived(), extension); if (event.getBridgedChannel() != null) { final AsteriskChannelImpl linkedChannel = getChannelImplByName(event.getBridgedChannel()); if (linkedChannel != null) { // the date used here is not correct! channel.channelLinked(event.getDateReceived(), linkedChannel); synchronized (linkedChannel) { linkedChannel.channelLinked(event.getDateReceived(), channel); } } } } if (isNew) { logger.info("Adding new channel " + channel.getName()); addChannel(channel); server.fireNewAsteriskChannel(channel); } } /** * Returns a channel from the ChannelManager's cache with the given name If multiple channels are * found, returns the most recently CREATED one. If two channels with the very same date exist, * avoid HUNGUP ones. * * @param name the name of the requested channel. * @return the (most recent) channel if found, in any state, or null if none found. */ AsteriskChannelImpl getChannelImplByName(String name) { Date dateOfCreation = null; AsteriskChannelImpl channel = null; if (name == null) { return null; } synchronized (channels) { for (AsteriskChannelImpl tmp : channels) { if (tmp.getName() != null && tmp.getName().equals(name)) { // return the most recent channel or when dates are similar, the active one if (dateOfCreation == null || tmp.getDateOfCreation().after(dateOfCreation) || (tmp.getDateOfCreation().equals(dateOfCreation) && tmp.getState() != ChannelState.HUNGUP)) { channel = tmp; dateOfCreation = channel.getDateOfCreation(); } } } } return channel; } /** * Returns a NON-HUNGUP channel from the ChannelManager's cache with the given name. * * @param name the name of the requested channel. * @return the NON-HUNGUP channel if found, or null if none is found. */ AsteriskChannelImpl getChannelImplByNameAndActive(String name) { // In non bristuffed AST 1.2, we don't have uniqueid header to match the channel // So we must use the channel name // Channel name is unique at any give moment in the * server // But asterisk-java keeps Hungup channels for a while. // We don't want to retrieve hungup channels. AsteriskChannelImpl channel = null; if (name == null) { return null; } synchronized (channels) { for (AsteriskChannelImpl tmp : channels) { if (tmp.getName() != null && tmp.getName().equals(name) && tmp.getState() != ChannelState.HUNGUP) { channel = tmp; } } } return channel; } AsteriskChannelImpl getChannelImplById(String id) { if (id == null) { return null; } synchronized (channels) { for (AsteriskChannelImpl channel : channels) { if (id.equals(channel.getId())) { return channel; } } } return null; } /** * Returns the other side of a local channel. * * <p>Local channels consist of two sides, like "Local/1234@from-local-60b5,1" and * "Local/1234@from-local-60b5,2" (for Asterisk 1.4) or "Local/1234@from-local-60b5;1" and * "Local/1234@from-local-60b5;2" (for Asterisk 1.6) this method returns the other side. * * @param localChannel one side * @return the other side, or <code>null</code> if not available or if the given channel is not a * local channel. */ AsteriskChannelImpl getOtherSideOfLocalChannel(AsteriskChannel localChannel) { final String name; final char num; if (localChannel == null) { return null; } name = localChannel.getName(); if (name == null || !name.startsWith("Local/") || (name.charAt(name.length() - 2) != ',' && name.charAt(name.length() - 2) != ';')) { return null; } num = name.charAt(name.length() - 1); if (num == '1') { return getChannelImplByName(name.substring(0, name.length() - 1) + "2"); } else if (num == '2') { return getChannelImplByName(name.substring(0, name.length() - 1) + "1"); } else { return null; } } void handleNewChannelEvent(NewChannelEvent event) { final AsteriskChannelImpl channel = getChannelImplById(event.getUniqueId()); if (channel == null) { if (event.getChannel() == null) { logger.info( "Ignored NewChannelEvent with empty channel name (uniqueId=" + event.getUniqueId() + ")"); } else { addNewChannel( event.getUniqueId(), event.getChannel(), event.getDateReceived(), event.getCallerIdNum(), event.getCallerIdName(), ChannelState.valueOf(event.getChannelState()), event.getAccountCode()); } } else { // channel had already been created probably by a NewCallerIdEvent synchronized (channel) { channel.nameChanged(event.getDateReceived(), event.getChannel()); channel.setCallerId(new CallerId(event.getCallerIdName(), event.getCallerIdNum())); channel.stateChanged( event.getDateReceived(), ChannelState.valueOf(event.getChannelState())); } } } void handleNewExtenEvent(NewExtenEvent event) { AsteriskChannelImpl channel; final Extension extension; channel = getChannelImplById(event.getUniqueId()); if (channel == null) { logger.error("Ignored NewExtenEvent for unknown channel " + event.getChannel()); return; } extension = new Extension( event.getContext(), event.getExtension(), event.getPriority(), event.getApplication(), event.getAppData()); synchronized (channel) { channel.extensionVisited(event.getDateReceived(), extension); } } void handleNewStateEvent(NewStateEvent event) { AsteriskChannelImpl channel = getChannelImplById(event.getUniqueId()); if (channel == null) { // NewStateEvent can occur for an existing channel that now has a different unique id // (originate with Local/) channel = getChannelImplByNameAndActive(event.getChannel()); if (channel != null) { logger.info( "Changing unique id for '" + channel.getName() + "' from " + channel.getId() + " to " + event.getUniqueId()); channel.idChanged(event.getDateReceived(), event.getUniqueId()); } if (channel == null) { logger.info( "Creating new channel due to NewStateEvent '" + event.getChannel() + "' unique id " + event.getUniqueId()); // NewStateEvent can occur instead of a NewChannelEvent channel = addNewChannel( event.getUniqueId(), event.getChannel(), event.getDateReceived(), event.getCallerIdNum(), event.getCallerIdName(), ChannelState.valueOf(event.getChannelState()), null /* account code not available */); } } // NewStateEvent can provide a new CallerIdNum or CallerIdName not previously received through a // NewCallerIdEvent. This happens at least on outgoing legs from the queue application to // agents. if (event.getCallerIdNum() != null || event.getCallerIdName() != null) { String cidnum = ""; String cidname = ""; CallerId currentCallerId = channel.getCallerId(); if (currentCallerId != null) { cidnum = currentCallerId.getNumber(); cidname = currentCallerId.getName(); } if (event.getCallerIdNum() != null) { cidnum = event.getCallerIdNum(); } if (event.getCallerIdName() != null) { cidname = event.getCallerIdName(); } CallerId newCallerId = new CallerId(cidname, cidnum); logger.debug("Updating CallerId (following NewStateEvent) to: " + newCallerId.toString()); channel.setCallerId(newCallerId); // Also, NewStateEvent can return a new channel name for the same channel uniqueid, indicating // the channel has been // renamed but no related RenameEvent has been received. // This happens with mISDN channels (see AJ-153) if (event.getChannel() != null && !event.getChannel().equals(channel.getName())) { logger.info( "Renaming channel (following NewStateEvent) '" + channel.getName() + "' to '" + event.getChannel() + "'"); synchronized (channel) { channel.nameChanged(event.getDateReceived(), event.getChannel()); } } } if (event.getChannelState() != null) { synchronized (channel) { channel.stateChanged( event.getDateReceived(), ChannelState.valueOf(event.getChannelState())); } } } void handleNewCallerIdEvent(NewCallerIdEvent event) { AsteriskChannelImpl channel = getChannelImplById(event.getUniqueId()); if (channel == null) { // NewCallerIdEvent can occur for an existing channel that now has a different unique id // (originate with Local/) channel = getChannelImplByNameAndActive(event.getChannel()); if (channel != null) { logger.info( "Changing unique id for '" + channel.getName() + "' from " + channel.getId() + " to " + event.getUniqueId()); channel.idChanged(event.getDateReceived(), event.getUniqueId()); } if (channel == null) { // NewCallerIdEvent can occur before NewChannelEvent channel = addNewChannel( event.getUniqueId(), event.getChannel(), event.getDateReceived(), event.getCallerIdNum(), event.getCallerIdName(), ChannelState.DOWN, null /* account code not available */); } } synchronized (channel) { channel.setCallerId(new CallerId(event.getCallerIdName(), event.getCallerIdNum())); } } void handleHangupEvent(HangupEvent event) { HangupCause cause = null; AsteriskChannelImpl channel = getChannelImplById(event.getUniqueId()); if (channel == null) { logger.error("Ignored HangupEvent for unknown channel " + event.getChannel()); return; } if (event.getCause() != null) { cause = HangupCause.getByCode(event.getCause()); } synchronized (channel) { channel.hungup(event.getDateReceived(), cause, event.getCauseTxt()); } logger.info("Removing channel " + channel.getName() + " due to hangup (" + cause + ")"); removeOldChannels(); } void handleDialEvent(DialEvent event) { final AsteriskChannelImpl sourceChannel = getChannelImplById(event.getUniqueId()); final AsteriskChannelImpl destinationChannel = getChannelImplById(event.getDestUniqueId()); if (sourceChannel == null) { if (event.getUniqueId() != null) { logger.error( "Ignored DialEvent for unknown source channel " + event.getChannel() + " with unique id " + event.getUniqueId()); } return; } if (destinationChannel == null) { if (event.getDestUniqueId() != null) { logger.error( "Ignored DialEvent for unknown destination channel " + event.getDestination() + " with unique id " + event.getDestUniqueId()); } return; } logger.info(sourceChannel.getName() + " dialed " + destinationChannel.getName()); getTraceId(sourceChannel); getTraceId(destinationChannel); synchronized (sourceChannel) { sourceChannel.channelDialed(event.getDateReceived(), destinationChannel); } synchronized (destinationChannel) { destinationChannel.channelDialing(event.getDateReceived(), sourceChannel); } } void handleBridgeEvent(BridgeEvent event) { final AsteriskChannelImpl channel1 = getChannelImplById(event.getUniqueId1()); final AsteriskChannelImpl channel2 = getChannelImplById(event.getUniqueId2()); if (channel1 == null) { logger.error("Ignored BridgeEvent for unknown channel " + event.getChannel1()); return; } if (channel2 == null) { logger.error("Ignored BridgeEvent for unknown channel " + event.getChannel2()); return; } if (event.isLink()) { logger.info("Linking channels " + channel1.getName() + " and " + channel2.getName()); synchronized (channel1) { channel1.channelLinked(event.getDateReceived(), channel2); } synchronized (channel2) { channel2.channelLinked(event.getDateReceived(), channel1); } } if (event.isUnlink()) { logger.info("Unlinking channels " + channel1.getName() + " and " + channel2.getName()); synchronized (channel1) { channel1.channelUnlinked(event.getDateReceived()); } synchronized (channel2) { channel2.channelUnlinked(event.getDateReceived()); } } } void handleRenameEvent(RenameEvent event) { AsteriskChannelImpl channel = getChannelImplById(event.getUniqueId()); if (channel == null) { logger.error("Ignored RenameEvent for unknown channel with uniqueId " + event.getUniqueId()); return; } logger.info( "Renaming channel '" + channel.getName() + "' to '" + event.getNewname() + "', uniqueId is " + event.getUniqueId()); synchronized (channel) { channel.nameChanged(event.getDateReceived(), event.getNewname()); } } void handleCdrEvent(CdrEvent event) { final AsteriskChannelImpl channel = getChannelImplById(event.getUniqueId()); final AsteriskChannelImpl destinationChannel = getChannelImplByName(event.getDestinationChannel()); final CallDetailRecordImpl cdr; if (channel == null) { logger.info("Ignored CdrEvent for unknown channel with uniqueId " + event.getUniqueId()); return; } cdr = new CallDetailRecordImpl(channel, destinationChannel, event); synchronized (channel) { channel.callDetailRecordReceived(event.getDateReceived(), cdr); } } private String getTraceId(AsteriskChannel channel) { String traceId; try { traceId = channel.getVariable(Constants.VARIABLE_TRACE_ID); } catch (Exception e) { traceId = null; } // logger.info("TraceId for channel " + channel.getName() + " is " + traceId); return traceId; } void handleParkedCallEvent(ParkedCallEvent event) { // Only bristuffed versions: AsteriskChannelImpl channel = // getChannelImplById(event.getUniqueId()); AsteriskChannelImpl channel = getChannelImplByNameAndActive(event.getChannel()); if (channel == null) { logger.info("Ignored ParkedCallEvent for unknown channel " + event.getChannel()); return; } synchronized (channel) { // todo The context should be "parkedcalls" or whatever has been configured in features.conf // unfortunately we don't get the context in the ParkedCallEvent so for now we'll set it to // null. Extension ext = new Extension(null, event.getExten(), 1); channel.setParkedAt(ext); logger.info( "Channel " + channel.getName() + " is parked at " + channel.getParkedAt().getExtension()); } } void handleParkedCallGiveUpEvent(ParkedCallGiveUpEvent event) { // Only bristuffed versions: AsteriskChannelImpl channel = // getChannelImplById(event.getUniqueId()); AsteriskChannelImpl channel = getChannelImplByNameAndActive(event.getChannel()); if (channel == null) { logger.info("Ignored ParkedCallGiveUpEvent for unknown channel " + event.getChannel()); return; } Extension wasParkedAt = channel.getParkedAt(); if (wasParkedAt == null) { logger.info("Ignored ParkedCallGiveUpEvent as the channel was not parked"); return; } synchronized (channel) { channel.setParkedAt(null); } logger.info( "Channel " + channel.getName() + " is unparked (GiveUp) from " + wasParkedAt.getExtension()); } void handleParkedCallTimeOutEvent(ParkedCallTimeOutEvent event) { // Only bristuffed versions: AsteriskChannelImpl channel = // getChannelImplById(event.getUniqueId()); final AsteriskChannelImpl channel = getChannelImplByNameAndActive(event.getChannel()); if (channel == null) { logger.info("Ignored ParkedCallTimeOutEvent for unknown channel " + event.getChannel()); return; } Extension wasParkedAt = channel.getParkedAt(); if (wasParkedAt == null) { logger.info("Ignored ParkedCallTimeOutEvent as the channel was not parked"); return; } synchronized (channel) { channel.setParkedAt(null); } logger.info( "Channel " + channel.getName() + " is unparked (Timeout) from " + wasParkedAt.getExtension()); } void handleUnparkedCallEvent(UnparkedCallEvent event) { // Only bristuffed versions: AsteriskChannelImpl channel = // getChannelImplById(event.getUniqueId()); final AsteriskChannelImpl channel = getChannelImplByNameAndActive(event.getChannel()); if (channel == null) { logger.info("Ignored UnparkedCallEvent for unknown channel " + event.getChannel()); return; } Extension wasParkedAt = channel.getParkedAt(); if (wasParkedAt == null) { logger.info("Ignored UnparkedCallEvent as the channel was not parked"); return; } synchronized (channel) { channel.setParkedAt(null); } logger.info( "Channel " + channel.getName() + " is unparked (moved away) from " + wasParkedAt.getExtension()); } void handleVarSetEvent(VarSetEvent event) { if (event.getUniqueId() == null) { return; } final AsteriskChannelImpl channel = getChannelImplById(event.getUniqueId()); if (channel == null) { logger.info("Ignored VarSetEvent for unknown channel with uniqueId " + event.getUniqueId()); return; } synchronized (channel) { channel.updateVariable(event.getVariable(), event.getValue()); } } void handleDtmfEvent(DtmfEvent event) { // we are only intrested in END events if (event.isBegin()) { return; } if (event.getUniqueId() == null) { return; } final AsteriskChannelImpl channel = getChannelImplById(event.getUniqueId()); if (channel == null) { logger.info("Ignored DtmfEvent for unknown channel with uniqueId " + event.getUniqueId()); return; } final Character dtmfDigit; if (event.getDigit() == null || event.getDigit().length() < 1) { dtmfDigit = null; } else { dtmfDigit = event.getDigit().charAt(0); } synchronized (channel) { if (event.isReceived()) { channel.dtmfReceived(dtmfDigit); } if (event.isSent()) { channel.dtmfSent(dtmfDigit); } } } }
/** * Abstract base class for common mapping strategies. <br> * If you implement your own mapping strategy you can derive from this class. * * @author srt * @since 0.3 */ public abstract class AbstractMappingStrategy implements MappingStrategy { /** Reference to Asterisk-Java's logging subsystem. */ protected Log logger = LogFactory.getLog(getClass()); private static final String[] DEFAULT_SCRIPT_PATH = new String[] {"agi"}; private ClassLoader defaultClassLoader = null; @Override public AgiScript determineScript(AgiRequest request, AgiChannel channel) { return determineScript(request); } public abstract AgiScript determineScript(AgiRequest request); /** * Returns the ClassLoader to use for loading AgiScript classes and load other resources like the * mapping properties file. * * <p>By default this method returns a class loader that searches for classes in the "agi" * subdirectory (if it exists) and uses the context class loader of the current thread as the * parent class loader. * * <p>You can override this method if you prefer using a different class loader. * * @return the ClassLoader to use for loading AgiScript classes and load other resources like the * mapping properties file. * @since 1.0.0 */ protected synchronized ClassLoader getClassLoader() { if (defaultClassLoader == null) { final ClassLoader parentClassLoader = Thread.currentThread().getContextClassLoader(); final List<URL> dirUrls = new ArrayList<URL>(); for (String scriptPathEntry : DEFAULT_SCRIPT_PATH) { final File scriptDir = new File(scriptPathEntry); if (!scriptDir.isDirectory()) { continue; } try { dirUrls.add(scriptDir.toURI().toURL()); } catch (MalformedURLException e) { // should not happen } } if (dirUrls.size() == 0) { return parentClassLoader; } defaultClassLoader = new URLClassLoader(dirUrls.toArray(new URL[dirUrls.size()]), parentClassLoader); } return defaultClassLoader; } /** * Creates a new instance of an AGI script. * * @param className Class name of the AGI script. The class must implement {@link AgiScript}. * @return the created instance of the AGI script class. If the instance can't be created an error * is logged and <code>null</code> is returned. */ @SuppressWarnings("unchecked") protected AgiScript createAgiScriptInstance(String className) { Class<?> tmpClass; Class<AgiScript> agiScriptClass; Constructor<AgiScript> constructor; AgiScript agiScript; agiScript = null; try { tmpClass = getClassLoader().loadClass(className); } catch (ClassNotFoundException e1) { logger.debug( "Unable to create AgiScript instance of type " + className + ": Class not found, make sure the class exists and is available on the CLASSPATH"); return null; } if (!AgiScript.class.isAssignableFrom(tmpClass)) { logger.warn( "Unable to create AgiScript instance of type " + className + ": Class does not implement the AgiScript interface"); return null; } agiScriptClass = (Class<AgiScript>) tmpClass; try { constructor = agiScriptClass.getConstructor(); agiScript = constructor.newInstance(); } catch (Exception e) { logger.warn("Unable to create AgiScript instance of type " + className, e); } return agiScript; } }
/** * Manages queue events on behalf of an AsteriskServer. * * @author srt * @version $Id$ */ class QueueManager { private final Log logger = LogFactory.getLog(this.getClass()); private final AsteriskServerImpl server; private final ChannelManager channelManager; private boolean queuesMonitorForced = false; private long queueMonitorLastTimeReloaded; private long queuesMonitorLeaseTime = 1000; /** * A map of ACD queues by there name. 101119 OLB: Modified to act as a LRU Cache to optimize * updates */ private final LinkedHashMap<String, AsteriskQueueImpl> queuesLRU = new LinkedHashMap<String, AsteriskQueueImpl>(); QueueManager(AsteriskServerImpl server, ChannelManager channelManager) { this.server = server; this.channelManager = channelManager; } void initialize() throws ManagerCommunicationException { ResponseEvents re; try { re = server.sendEventGeneratingAction(new QueueStatusAction()); } catch (ManagerCommunicationException e) { final Throwable cause = e.getCause(); if (cause instanceof EventTimeoutException) { // this happens with Asterisk 1.0.x as it doesn't send a // QueueStatusCompleteEvent re = ((EventTimeoutException) cause).getPartialResult(); } else { throw e; } } for (ManagerEvent event : re.getEvents()) { if (event instanceof QueueParamsEvent) { handleQueueParamsEvent((QueueParamsEvent) event); } else if (event instanceof QueueMemberEvent) { handleQueueMemberEvent((QueueMemberEvent) event); } else if (event instanceof QueueEntryEvent) { handleQueueEntryEvent((QueueEntryEvent) event); } } } /** * Method to ask for a Queue data update * * @author Octavio Luna * @param queue * @throws ManagerCommunicationException */ void updateQueue(String queue) throws ManagerCommunicationException { ResponseEvents re; try { QueueStatusAction queueStatusAction = new QueueStatusAction(); queueStatusAction.setQueue(queue); re = server.sendEventGeneratingAction(queueStatusAction); } catch (ManagerCommunicationException e) { final Throwable cause = e.getCause(); if (cause instanceof EventTimeoutException) { // this happens with Asterisk 1.0.x as it doesn't send a // QueueStatusCompleteEvent re = ((EventTimeoutException) cause).getPartialResult(); } else { throw e; } } for (ManagerEvent event : re.getEvents()) { // 101119 OLB: solo actualizamos el QUEUE por ahora if (event instanceof QueueParamsEvent) { handleQueueParamsEvent((QueueParamsEvent) event); } else if (event instanceof QueueMemberEvent) { handleQueueMemberEvent((QueueMemberEvent) event); } else if (event instanceof QueueEntryEvent) { handleQueueEntryEvent((QueueEntryEvent) event); } } } void disconnected() { synchronized (queuesLRU) { for (AsteriskQueueImpl queue : queuesLRU.values()) { queue.cancelServiceLevelTimer(); } queuesLRU.clear(); } } /** * Gets (a copy of) the list of the queues. * * @return a copy of the list of the queues. */ Collection<AsteriskQueue> getQueues() { refreshQueuesIfForced(); Collection<AsteriskQueue> copy; synchronized (queuesLRU) { copy = new ArrayList<AsteriskQueue>(queuesLRU.values()); } return copy; } public List<AsteriskQueue> getQueuesUpdatedAfter(Date date) { refreshQueuesIfForced(); List<AsteriskQueue> copy = new ArrayList<AsteriskQueue>(); synchronized (queuesLRU) { List<Entry<String, AsteriskQueueImpl>> list = new ArrayList<Entry<String, AsteriskQueueImpl>>(queuesLRU.entrySet()); ListIterator<Entry<String, AsteriskQueueImpl>> iter = list.listIterator(list.size()); Entry<String, AsteriskQueueImpl> entry; while (iter.hasPrevious()) { entry = iter.previous(); AsteriskQueueImpl astQueue = entry.getValue(); if (astQueue.getLastUpdateMillis() <= date.getTime()) { break; } copy.add(astQueue); } } return copy; } /** * Adds a queue to the internal map, keyed by name. * * @param queue the AsteriskQueueImpl to be added */ private void addQueue(AsteriskQueueImpl queue) { synchronized (queuesLRU) { queuesLRU.put(queue.getName(), queue); } } /** * Called during initialization to populate the list of queues. * * @param event the event received */ private void handleQueueParamsEvent(QueueParamsEvent event) { AsteriskQueueImpl queue; final String name = event.getQueue(); final Integer max = event.getMax(); final String strategy = event.getStrategy(); final Integer serviceLevel = event.getServiceLevel(); final Integer weight = event.getWeight(); final Integer calls = event.getCalls(); final Integer holdTime = event.getHoldTime(); final Integer talkTime = event.getTalkTime(); final Integer completed = event.getCompleted(); final Integer abandoned = event.getAbandoned(); final Double serviceLevelPerf = event.getServiceLevelPerf(); queue = getInternalQueueByName(name); if (queue == null) { queue = new AsteriskQueueImpl( server, name, max, strategy, serviceLevel, weight, calls, holdTime, talkTime, completed, abandoned, serviceLevelPerf); logger.info("Adding new queue " + queue); addQueue(queue); } else { // We should never reach that code as this method is only called for initialization // So the queue should never be in the queues list synchronized (queue) { synchronized (queuesLRU) { if (queue.setMax(max) | queue.setServiceLevel(serviceLevel) | queue.setWeight(weight) | queue.setCalls(calls) | queue.setHoldTime(holdTime) | queue.setTalkTime(talkTime) | queue.setCompleted(completed) | queue.setAbandoned(abandoned) | queue.setServiceLevelPerf(serviceLevelPerf)) { queuesLRU.remove(queue.getName()); queuesLRU.put(queue.getName(), queue); } } } } } /** * Called during initialization to populate the members of the queues. * * @param event the QueueMemberEvent received */ private void handleQueueMemberEvent(QueueMemberEvent event) { final AsteriskQueueImpl queue = getInternalQueueByName(event.getQueue()); if (queue == null) { logger.error("Ignored QueueEntryEvent for unknown queue " + event.getQueue()); return; } AsteriskQueueMemberImpl member = queue.getMember(event.getLocation()); if (member == null) { member = new AsteriskQueueMemberImpl( server, queue, event.getLocation(), QueueMemberState.valueOf(event.getStatus()), event.getPaused(), event.getPenalty(), event.getMembership(), event.getCallsTaken(), event.getLastCall()); queue.addMember(member); } else { if (member.stateChanged(QueueMemberState.valueOf(event.getStatus())) | member.pausedChanged(event.getPaused()) | member.penaltyChanged(event.getPenalty()) | member.callsTakenChanged(event.getCallsTaken()) | member.lastCallChanged(event.getLastCall())) { queue.stampLastUpdate(); } } } /** * Called during initialization to populate entries of the queues. Currently does the same as * handleJoinEvent() * * @param event - the QueueEntryEvent received */ private void handleQueueEntryEvent(QueueEntryEvent event) { final AsteriskQueueImpl queue = getInternalQueueByName(event.getQueue()); final AsteriskChannelImpl channel = channelManager.getChannelImplByName(event.getChannel()); if (queue == null) { logger.error("Ignored QueueEntryEvent for unknown queue " + event.getQueue()); return; } if (channel == null) { logger.error("Ignored QueueEntryEvent for unknown channel " + event.getChannel()); return; } if (queue.getEntry(event.getChannel()) != null) { logger.debug( "Ignored duplicate queue entry during population in queue " + event.getQueue() + " for channel " + event.getChannel()); return; } // Asterisk gives us an initial position but doesn't tell us when he shifts the others // We won't use this data for ordering until there is a appropriate event in AMI. // (and refreshing the whole queue is too intensive and suffers incoherencies // due to asynchronous shift that leaves holes if requested too fast) int reportedPosition = event.getPosition(); queue.createNewEntry(channel, reportedPosition, event.getDateReceived()); } /** * Called from AsteriskServerImpl whenever a new entry appears in a queue. * * @param event the JoinEvent received */ void handleJoinEvent(JoinEvent event) { final AsteriskQueueImpl queue = getInternalQueueByName(event.getQueue()); final AsteriskChannelImpl channel = channelManager.getChannelImplByName(event.getChannel()); if (queue == null) { logger.error("Ignored JoinEvent for unknown queue " + event.getQueue()); return; } if (channel == null) { logger.error("Ignored JoinEvent for unknown channel " + event.getChannel()); return; } if (queue.getEntry(event.getChannel()) != null) { logger.error( "Ignored duplicate queue entry in queue " + event.getQueue() + " for channel " + event.getChannel()); return; } // Asterisk gives us an initial position but doesn't tell us when he shifts the others // We won't use this data for ordering until there is a appropriate event in AMI. // (and refreshing the whole queue is too intensive and suffers incoherencies // due to asynchronous shift that leaves holes if requested too fast) int reportedPosition = event.getPosition(); queue.createNewEntry(channel, reportedPosition, event.getDateReceived()); } /** * Called from AsteriskServerImpl whenever an enty leaves a queue. * * @param event - the LeaveEvent received */ void handleLeaveEvent(LeaveEvent event) { final AsteriskQueueImpl queue = getInternalQueueByName(event.getQueue()); final AsteriskChannelImpl channel = channelManager.getChannelImplByName(event.getChannel()); if (queue == null) { logger.error("Ignored LeaveEvent for unknown queue " + event.getQueue()); return; } if (channel == null) { logger.error("Ignored LeaveEvent for unknown channel " + event.getChannel()); return; } final AsteriskQueueEntryImpl existingQueueEntry = queue.getEntry(event.getChannel()); if (existingQueueEntry == null) { logger.error( "Ignored leave event for non existing queue entry in queue " + event.getQueue() + " for channel " + event.getChannel()); return; } queue.removeEntry(existingQueueEntry, event.getDateReceived()); } /** * Challange a QueueMemberStatusEvent. Called from AsteriskServerImpl whenever a member state * changes. * * @param event that was triggered by Asterisk server. */ void handleQueueMemberStatusEvent(QueueMemberStatusEvent event) { AsteriskQueueImpl queue = getInternalQueueByName(event.getQueue()); if (queue == null) { logger.error("Ignored QueueMemberStatusEvent for unknown queue " + event.getQueue()); return; } AsteriskQueueMemberImpl member = queue.getMemberByLocation(event.getLocation()); if (member == null) { logger.error("Ignored QueueMemberStatusEvent for unknown member " + event.getLocation()); return; } updateQueue(queue.getName()); member.stateChanged(QueueMemberState.valueOf(event.getStatus())); member.penaltyChanged(event.getPenalty()); member.lastCallChanged(event.getLastCall()); member.callsTakenChanged(event.getCallsTaken()); queue.fireMemberStateChanged(member); } void handleQueueMemberPausedEvent(QueueMemberPausedEvent event) { AsteriskQueueImpl queue = getInternalQueueByName(event.getQueue()); if (queue == null) { logger.error("Ignored QueueMemberPausedEvent for unknown queue " + event.getQueue()); return; } AsteriskQueueMemberImpl member = queue.getMemberByLocation(event.getLocation()); if (member == null) { logger.error("Ignored QueueMemberPausedEvent for unknown member " + event.getLocation()); return; } member.pausedChanged(event.getPaused()); } void handleQueueMemberPenaltyEvent(QueueMemberPenaltyEvent event) { AsteriskQueueImpl queue = getInternalQueueByName(event.getQueue()); if (queue == null) { logger.error("Ignored QueueMemberStatusEvent for unknown queue " + event.getQueue()); return; } AsteriskQueueMemberImpl member = queue.getMemberByLocation(event.getLocation()); if (member == null) { logger.error("Ignored QueueMemberStatusEvent for unknown member " + event.getLocation()); return; } member.penaltyChanged(event.getPenalty()); } /** * Retrieves a queue by its name. * * @param queueName name of the queue. * @return the requested queue or <code>null</code> if there is no queue with the given name. */ AsteriskQueueImpl getQueueByName(String queueName) { refreshQueueIfForced(queueName); AsteriskQueueImpl queue = getInternalQueueByName(queueName); if (queue == null) { logger.error("Requested queue '" + queueName + "' not found!"); } return queue; } private AsteriskQueueImpl getInternalQueueByName(String queueName) { AsteriskQueueImpl queue; synchronized (queuesLRU) { queue = queuesLRU.get(queueName); } return queue; } private void refreshQueueIfForced(String queueName) { if (queuesMonitorForced) { updateQueue(queueName); } } private void refreshQueuesIfForced() { if (queuesMonitorForced) { if ((System.currentTimeMillis() - queueMonitorLastTimeReloaded) > queuesMonitorLeaseTime) { initialize(); queueMonitorLastTimeReloaded = System.currentTimeMillis(); } } } /** * Challange a QueueMemberAddedEvent. * * @param event - the generated QueueMemberAddedEvent. */ public void handleQueueMemberAddedEvent(QueueMemberAddedEvent event) { final AsteriskQueueImpl queue = getInternalQueueByName(event.getQueue()); if (queue == null) { logger.error("Ignored QueueMemberAddedEvent for unknown queue " + event.getQueue()); return; } AsteriskQueueMemberImpl member = queue.getMember(event.getLocation()); if (member == null) { member = new AsteriskQueueMemberImpl( server, queue, event.getLocation(), QueueMemberState.valueOf(event.getStatus()), event.getPaused(), event.getPenalty(), event.getMembership(), event.getCallsTaken(), event.getLastCall()); } queue.addMember(member); } /** * Challange a QueueMemberRemovedEvent. * * @param event - the generated QueueMemberRemovedEvent. */ public void handleQueueMemberRemovedEvent(QueueMemberRemovedEvent event) { final AsteriskQueueImpl queue = getInternalQueueByName(event.getQueue()); if (queue == null) { logger.error("Ignored QueueMemberRemovedEvent for unknown queue " + event.getQueue()); return; } final AsteriskQueueMemberImpl member = queue.getMember(event.getLocation()); if (member == null) { logger.error( "Ignored QueueMemberRemovedEvent for unknown agent name: " + event.getMemberName() + " location: " + event.getLocation() + " queue: " + event.getQueue()); return; } queue.removeMember(member); } public void forceQueuesMonitor(boolean force) { queuesMonitorForced = force; } public boolean isQueuesMonitorForced() { return queuesMonitorForced; } }