/**
  * Update 'global' JMS clients and RDB
  *
  * @param pv Alarm PV
  * @param severity Alarm severity (highest, latched)
  * @param message Alarm message
  * @param value Value that triggered
  * @param timestamp Time of last alarm update
  */
 public void sendGlobalUpdate(
     final AlarmPV pv,
     final SeverityLevel severity,
     final String message,
     final String value,
     final Timestamp timestamp) {
   // Send to JMS
   messenger.sendGlobalUpdate(pv, severity, message, value, timestamp);
   // Persist global alarm state change in separate queue & thread
   // so that it won't delay the alarm server from updating
   work_queue.execute(
       new Runnable() {
         @Override
         public void run() {
           try {
             rdb.writeGlobalUpdate(pv, severity.isActive());
             recoverFromRDBErrors();
           } catch (Exception ex) {
             // Remember that there was an error
             had_RDB_error = true;
             Activator.getLogger().log(Level.SEVERE, "Exception during global alarm update", ex);
           }
         }
       });
 }
  /**
   * Update JMS clients and RDB
   *
   * @param pv Alarm PV
   * @param current_severity Current channel severity
   * @param current_message Current message
   * @param severity Alarm severity (highest, latched)
   * @param message Alarm message
   * @param value Value that triggered
   * @param timestamp Time of last alarm update
   */
  public void sendStateUpdate(
      final AlarmPV pv,
      final SeverityLevel current_severity,
      final String current_message,
      final SeverityLevel severity,
      final String message,
      final String value,
      final Timestamp timestamp) {
    messenger.sendStateUpdate(
        pv, current_severity, current_message, severity, message, value, timestamp);
    // Move the persistence of states into separate queue & thread
    // so that it won't delay the alarm server from updating.

    // ReplacableRunnable:
    // If there is already an update request for this PV on the queue,
    // replace it with the new one because it's out of date and no
    // longer needs to be writting to the RDB anyway.
    work_queue.executeReplacable(
        new ReplacableRunnable<AlarmPV>(pv) {
          @Override
          public void run() {
            try {
              rdb.writeStateUpdate(
                  pv, current_severity, current_message, severity, message, value, timestamp);
              recoverFromRDBErrors();
            } catch (Exception ex) {
              // Remember that there was an error
              had_RDB_error = true;
              Activator.getLogger().log(Level.SEVERE, "Exception during alarm state update", ex);
            }
          }
        });
  }
  /** Invoked for received messages */
  private void handleMapMessage(final MapMessage message) {
    try {
      final String text = message.getString(JMSLogMessage.TEXT);
      Runnable action = null;

      // Create action, or handle the message right away

      // Alarm state change?
      if (JMSAlarmMessage.TEXT_STATE.equals(text)) {
        // Received a state update from server, reset timeout
        timeout_timer.reset();
        action = new UpdateAction(AlarmUpdateInfo.fromMapMessage(message));
        model.updateServerState(false);
      } else if (JMSAlarmMessage.TEXT_STATE_MAINTENANCE.equals(text)) {
        timeout_timer.reset();
        action = new UpdateAction(AlarmUpdateInfo.fromMapMessage(message));
        model.updateServerState(true);
      }
      // Idle messages in absence of 'real' traffic?
      else if (JMSAlarmMessage.TEXT_IDLE.equals(text)) {
        timeout_timer.reset();
        model.updateServerState(false);
      } else if (JMSAlarmMessage.TEXT_IDLE_MAINTENANCE.equals(text)) {
        timeout_timer.reset();
        model.updateServerState(true);
      }
      // Enable/disable?
      else if (JMSAlarmMessage.TEXT_ENABLE.equals(text)) {
        timeout_timer.reset();
        final String name = message.getString(JMSLogMessage.NAME);
        action = new EnableAction(name, true);
      } else if (JMSAlarmMessage.TEXT_DISABLE.equals(text)) {
        timeout_timer.reset();
        final String name = message.getString(JMSLogMessage.NAME);
        action = new EnableAction(name, false);
      }
      // Configuration change
      else if (JMSAlarmMessage.TEXT_CONFIG.equals(text)) {
        final String name = message.getString(JMSLogMessage.NAME);
        model.readConfig(name);
      }
      // Debug trigger
      else if (JMSAlarmMessage.TEXT_DEBUG.equals(text)) model.dump();

      if (action == null) return;
      // Queue or dispatch?
      synchronized (queue) {
        if (use_queue) {
          queue.execute(action);
          return;
        }
      }
      // else: Not using queue, and queue no longer locked
      action.run();
    } catch (Throwable ex) {
      Activator.getLogger().log(Level.SEVERE, "Message handler error", ex);
    }
  }
  /**
   * If this is the first successful RDB update after errors, tell everybody to re-load the
   * configuration because otherwise they get out of sync.
   *
   * @throws Exception on error
   */
  protected void recoverFromRDBErrors() throws Exception {
    if (!had_RDB_error) return;

    // We should be on the work queue thread
    work_queue.assertOnThread();
    Activator.getLogger().info("RDB connection recovered, re-loading configuration");
    updateConfig(null);

    // If that worked out, reset error and inform clients
    had_RDB_error = false;
    messenger.sendReloadMessage();
  }
 /**
  * Update JMS clients and RDB about 'enabled' state of PV
  *
  * @param pv Alarm PV
  * @param enabled Enabled or not?
  */
 public void sendEnablementUpdate(final AlarmPV pv, final boolean enabled) {
   messenger.sendEnablementUpdate(pv, enabled);
   // Handle in separate queue & thread
   work_queue.execute(
       new Runnable() {
         @Override
         public void run() {
           try {
             rdb.writeEnablementUpdate(pv, enabled);
             recoverFromRDBErrors();
           } catch (Exception ex) {
             // Remember that there was an error
             had_RDB_error = true;
             Activator.getLogger().log(Level.SEVERE, "Exception during enablement update", ex);
           }
         }
       });
 }
  /** Dump all PVs to stdout */
  public void dump() {
    System.out.println("== Alarm Server PV Snapshot ==");
    synchronized (this) {
      alarm_tree.dump(System.out);
    }

    System.out.println("Work queue size: " + work_queue.size());

    // Log memory usage in MB
    final double free = Runtime.getRuntime().freeMemory() / (1024.0 * 1024.0);
    final double total = Runtime.getRuntime().totalMemory() / (1024.0 * 1024.0);
    final double max = Runtime.getRuntime().maxMemory() / (1024.0 * 1024.0);

    final DateFormat format = new SimpleDateFormat(JMSLogMessage.DATE_FORMAT);
    System.out.format(
        "%s == Alarm Server Memory: Max %.2f MB, Free %.2f MB (%.1f %%), total %.2f MB (%.1f %%)\n",
        format.format(new Date()), max, free, 100.0 * free / max, total, 100.0 * total / max);
  }
 /**
  * Dispatch queued events, or directly dispatch received events?
  *
  * <p>While reading model information, queue mode should be enabled. Then, after model information
  * from RDB has been obtained, disable queue mode which also dispatched all the queued up events.
  *
  * @param use_queue <code>true</code> to queue
  */
 public void setQueueMode(final boolean use_queue) {
   this.use_queue = use_queue;
   if (use_queue) return;
   // Queuing turned off -> Dispatched what has accumulated until now
   queue.performQueuedCommands();
 }