Example #1
0
/**
 * 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());
  }
}
Example #3
0
/**
 * 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;
  }
}
Example #5
0
/**
 * 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;
  }
}