/*
* ALMA - Atacama Large Millimiter Array (c) European Southern Observatory, 2011
*
* This library is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 2.1 of the License, or (at your option)
* any later version.
*
* This library is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
package alma.acs.alarm.gui.senderpanel;
import java.sql.Timestamp;
import java.util.Collections;
import java.util.HashSet;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.logging.Logger;
import alma.acs.alarm.gui.senderpanel.ParallelAlarmSender.AlarmToSend;
import alma.acs.alarm.gui.senderpanel.SenderPanelUtils.AlarmDescriptorType;
import alma.acs.alarm.gui.senderpanel.SenderPanelUtils.Triplet;
import alma.acs.container.ContainerServices;
import alma.acs.logging.AcsLogLevel;
import alma.acsErrTypeAlarmSourceFactory.ACSASFactoryNotInitedEx;
import alma.acsErrTypeAlarmSourceFactory.FaultStateCreationErrorEx;
import alma.acsErrTypeAlarmSourceFactory.SourceCreationErrorEx;
import alma.alarmsystem.source.ACSAlarmSystemInterface;
import alma.alarmsystem.source.ACSAlarmSystemInterfaceFactory;
import alma.alarmsystem.source.ACSFaultState;
/**
* The class to send alarms to the alarm service.
* <P>
* The sending is done by a dedicated thread to avoid blocking the caller.
* Alarms are queued and published in a FIFO order.
* The size of the queue is limited to {@value #maxAlarmsToQueue} that is enough for testing
* purposes of this class.
* If the queues is full, the caller methods waits until there is enough room in the queue.
* <P>
* AlarmSender objects notify the listener whenever an alarm has been published.
* <P>
* Life cycle:
*
* @author acaproni
*
*/
public class AlarmSender implements Runnable {
/**
* The max number of alarms that can stored in the queue for sending.
*/
private static final int maxAlarmsToQueue=50000;
/**
* The number of msecs between one alarm sending and another.
* This avoid flooding the system.
*/
private long TIME_BETWEEN_SENDINGS=100;
/**
* The time (msec) between two log messages showing the number
* of alarms sent by the class
*/
private final long TIME_BETWEEN_LOGS=60000;
/**
* / Log a message every minute reporting the number of alarms published
*/
private long lastLogMessageTime=System.currentTimeMillis();
/**
* The total number of alarms sent by this sender
*/
private int numOfAlarmsSent=0;
/**
* Signal if the sender is closed;
*/
private volatile boolean closed=false;
/**
* The queue of alarms to send.
* <P>
* Alarms to be sent are stored in this queue.
* The thread gets alarms from this queue and publishes them in the source NC.
*/
private final BlockingQueue<AlarmToSend> alarmsToSend = new ArrayBlockingQueue<AlarmToSend>(maxAlarmsToQueue);
/**
* The listeners to notify when alarms have been sent to the alarm system.
*/
private final Set<AlarmSentListener> listeners = Collections.synchronizedSet(new HashSet<AlarmSentListener>());
/**
* The source to send alarms
*/
private final ACSAlarmSystemInterface alarmSource;
/**
* The logger
*/
private final Logger logger;
/**
* The ContainerServices
*/
private final ContainerServices contSvcs;
/**
* The thread to send alarms
* @see #run()
*/
private Thread thread=null;
/**
* The name of the thread
*/
private final String threadName;
/**
* Constructor
*
* @param svcs The {@link ContainerServices}
* @param name The name of the sender
* @throws SourceCreationErrorEx
* @throws ACSASFactoryNotInitedEx
*/
public AlarmSender(ContainerServices svcs, String name) throws ACSASFactoryNotInitedEx, SourceCreationErrorEx {
if (svcs==null) {
throw new IllegalArgumentException("The ContainerServices can't be null");
}
if (name==null || name.isEmpty()) {
throw new IllegalArgumentException("Invalid name of sender");
}
alarmSource=ACSAlarmSystemInterfaceFactory.createSource(this.getClass().getName());
this.contSvcs=svcs;
this.threadName=name;
this.logger=svcs.getLogger();
}
/**
* Constructor
*
* @param svcs The {@link ContainerServices}
* @param name The name of the sender
* @param The time between sending 2 alarms
* @throws SourceCreationErrorEx
* @throws ACSASFactoryNotInitedEx
*/
public AlarmSender(ContainerServices svcs, String name, long timeBetweenAlarms)
throws ACSASFactoryNotInitedEx, SourceCreationErrorEx {
this(svcs,name);
if (timeBetweenAlarms<0) {
throw new IllegalArgumentException("Invalid time between two alarms sending: "+timeBetweenAlarms);
}
TIME_BETWEEN_SENDINGS=timeBetweenAlarms;
}
/**
* Synchronously publishes the specified alarm in the source NC.
* <P>
* Sending alarms through this method should be generally avoided, preferring
* {@link #send(Triplet, AlarmDescriptorType, Properties)} instead.
*
* @param triplet The triplet in the form FF,FM,FC
* @param The descriptor
* @param props The user properties
* @throws FaultStateCreationErrorEx
* @throws ACSASFactoryNotInitedEx
*/
protected void sendSynch(Triplet triplet, AlarmDescriptorType descriptor, Properties props) {
if (triplet==null) {
throw new IllegalArgumentException("The Triplet can't be null");
}
if (descriptor==null) {
throw new IllegalArgumentException("The descriptor can't be null");
}
if (closed) {
return;
}
logger.log(AcsLogLevel.DELOUSE, "Sending alarm "+triplet.toString()+" with descriptor "+descriptor);
ACSFaultState state =null;
try {
state=ACSAlarmSystemInterfaceFactory.createFaultState(triplet.faultFamily,triplet.faultMember,triplet.faultCode);
} catch (Throwable t) {
notifyListeners(triplet, descriptor,false);
logger.log(AcsLogLevel.ERROR, "Alarm "+triplet.toString()+" sent with descriptor "+descriptor,t);
return;
}
state.setDescriptor(descriptor.descriptor);
state.setUserTimestamp(new Timestamp(System.currentTimeMillis()));
if (props!=null && !props.isEmpty()) {
state.setUserProperties(props);
}
alarmSource.push(state);
notifyListeners(triplet, descriptor,true);
logger.log(AcsLogLevel.DELOUSE, "Alarm "+triplet.toString()+" sent");
}
/**
* Send the specified alarm to the alarm service.
* <P>
* This method return immediately: each alarm is queued and sent later on by the thread.
* <P>If the queue of alarms contains more then {@value #maxAlarmsToQueue}, the caller waits until
* the thread frees the queue.
*
* @param alarmToSend The alarm to send to the alarm server
* @throws InterruptedException If interrupted while awaiting to put the alarm in the queue
*/
public void send(AlarmToSend alarmToSend) throws InterruptedException {
if (alarmToSend==null) {
throw new IllegalArgumentException("The alarm to end can't be null");
}
if (!closed) {
alarmsToSend.put(alarmToSend);
}
}
/**
* Add a listener
*
* @param listener The listener to notify when an alarm has been sent
* @return <code>true</code> if this set did not already contain the specified element
*/
public boolean addListener(AlarmSentListener listener) {
if (listener==null) {
throw new IllegalArgumentException("The listener can't be null");
}
return listeners.add(listener);
}
/**
* Remove a listener
*
* @param listener The listener to notify when an alarm has been sent
* @return <code>true</code> if this set contained the specified element
*/
public boolean removeListener(AlarmSentListener listener) {
if (listener==null) {
throw new IllegalArgumentException("The listener can't be null");
}
return listeners.remove(listener);
}
/**
* Notify the listeners that an alarm has been sent to the alarm service
*
* @param triplet The triplet of the alarm
* @param descriptor The descriptor (i.e. activation type
* @param success is <code>true</code> if the alarm has been successfully published;
* <code>false</code> otherwise
*/
private void notifyListeners(Triplet triplet, AlarmDescriptorType descriptor, boolean success) {
if (triplet==null || descriptor==null) {
throw new IllegalArgumentException("Invalid null triplet and/or descriptor");
}
synchronized (listeners) {
for (AlarmSentListener listener: listeners) {
listener.alarmSent(triplet, descriptor,success);
}
}
}
/**
* Start the thread.
*/
public synchronized void start() {
ThreadFactory threadFactory=contSvcs.getThreadFactory();
thread = threadFactory.newThread(this);
thread.setName(threadName);
thread.start();
}
/**
* Close the sender and the thread
*/
public synchronized void close() {
closed=true;
alarmsToSend.clear();
if (thread!=null) {
thread.interrupt();
}
alarmSource.close();
}
/**
* Return the number of alars queued and waiting to be sent
* @return
*/
public int alarmsWaiting() {
return alarmsToSend.size();
}
/**
* The thread to get alarms from queue and publish them into the NC
*/
@Override
public void run() {
logger.log(AcsLogLevel.DEBUG,"Thread "+threadName+" started");
while (!closed) {
AlarmToSend alarm = null;
try {
alarm=alarmsToSend.take();
if (TIME_BETWEEN_SENDINGS>0) {
Thread.sleep(TIME_BETWEEN_SENDINGS);
}
} catch (InterruptedException ie) {
continue;
}
try {
sendSynch(alarm.triplet, alarm.descriptor, alarm.userProperties);
numOfAlarmsSent++;
} catch (Throwable t) {
logger.log(AcsLogLevel.ERROR,threadName+": Error sending alarm",t);
}
if (System.currentTimeMillis()-lastLogMessageTime>TIME_BETWEEN_LOGS) {
lastLogMessageTime=System.currentTimeMillis();
logger.log(AcsLogLevel.DEBUG, threadName+": alarms sent: "+numOfAlarmsSent+", alarms waiting to be sent: "+alarmsToSend.size());
}
}
logger.log(AcsLogLevel.DEBUG,"Thread "+threadName+" terminated");
}
}