/*
* ALMA - Atacama Large Millimiter Array (c) European Southern Observatory, 2012
*
* 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.util.Collections;
import java.util.HashSet;
import java.util.Properties;
import java.util.Random;
import java.util.Set;
import java.util.Vector;
import java.util.concurrent.ThreadFactory;
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;
/**
* The base class for alarms senders.
* <P>
* {@link BaseAlarmsSender} starts a thread, {@link #alarmsSenderThread}, to randomly set/clear the alarms whose definition
* is read from the file.<BR>
* There is only one thread alive at a given time. So if the user want to start a task,
* he must wait the termination of the current task or stop it.
* <P>
* Default fault members have a '*' in the FM of the triplet. Default fault members are used only while cycling
* i.e. when activating or terminating all the alarms they are ignored.
* The fault member name of a default is built by appending a random number to the prefix name, {@link #defaultFMNamePrefix}.
* This means that the user must clean default fault members by using the table of alarms.
*
*
*
* @author acaproni
*
*/
public class BaseAlarmsSender {
/**
* An alarm read from the file or the TM/CDB.
* <P>
* The descriptor for the activation is not present because
* it is generated depending on the button the user pressed.
*
* {@link AlarmRead} is immutable.
*
* @author acaproni
*
*/
protected static final class AlarmRead {
/**
* The triplet "FF,FM,FC"
*/
public final String tripletStr;
/**
* The alarm triplet
*/
public final Triplet triplet;
/**
* The properties "key=val1, key2=val2..."
*/
public final String props;
/**
* The properties built parsing {@link #props}.
*/
public final Properties properties;
/**
* Constructor.
* <P>
* Build the {@link AlarmRead} object form the strings.
* None of the parameters can contain a comment (i.e. "#...").
*
*
* @param triplet The triplet
* @param props The user properties
* @throws Exception if the triplet is invalid
*/
public AlarmRead(String triplet,String props) throws Exception{
if (triplet==null || triplet.isEmpty() || !SenderPanelUtils.isATriplet(triplet)) {
throw new Exception("Invalid triplet "+triplet);
}
this.tripletStr=triplet.trim();
this.triplet=SenderPanelUtils.tripletFromString(tripletStr);
this.props=props;
Properties temp=null;
if (SenderPanelUtils.isAStringOfProperties(props)) {
temp=SenderPanelUtils.propertiesFromString(props.trim());
}
properties=temp;
}
/**
* @return <code>true</code> if there is a default fault member
*/
public boolean isDefaultFM() {
return triplet.faultMember.equals("*");
}
/**
* Print an alarm
*/
@Override
public String toString() {
StringBuilder str = new StringBuilder();
str.append('<');
str.append(tripletStr);
str.append("> Properties=[");
str.append(props);
str.append("]");
if (properties!=null && !properties.isEmpty()) {
str.append(" ("+properties.size()+" properties)");
}
return str.toString();
}
}
/**
* The thread factory to create threads for sending alarms.
* <P>
* The purpose of this class is to create the threads with the same settings.
*
* @author acaproni
*/
protected static final class AlarmSenderThreadFactory implements ThreadFactory {
/**
* The number of the thread used to build its name
*/
private int threadNumber=0;
/**
* The prefix of the name of the thread
*/
private final String threadNamePrefix;
/**
*
* @param threadNamePrefix The prefix of the name of the thread
*/
public AlarmSenderThreadFactory(String threadNamePrefix) {
this.threadNamePrefix=threadNamePrefix;
}
/**
* @see ThreadFactory#newThread(Runnable)
*/
@Override
public synchronized Thread newThread(Runnable r) {
if (r==null) {
throw new IllegalArgumentException("Impossible to create a thread from a null Runnable");
}
threadNumber++;
Thread t = new Thread(r,threadNamePrefix+threadNumber);
t.setDaemon(true);
return t;
}
}
/**
* The prefix of a fault member generated from a default
*/
protected final String defaultFMNamePrefix="DefaultFM_";
/**
* The max number appended to the prefix to build a fault member with a default
* <P>
* This is also the max number of alarms that can be generated for a default fault member
* i.e. an alarm like FF,*,1 can be translated into at most {@link #maxDefaultFMSuffix} alarms
*/
protected final int maxDefaultFMSuffix=1000;
/**
* ContainerServices
*/
protected final ContainerServices contSvcs;
/**
* The object to send alarms
*/
protected final ParallelAlarmSender alarmsSender;
/**
* The parent component to show the dialog
*/
protected final SenderPanel panel;
/**
* The factory to create threads
*/
protected final AlarmSenderThreadFactory threadFactory;
/**
* The thread to send alarms.
*/
protected volatile Thread alarmsSenderThread=null;
/**
* The alarms read from the file.
*/
protected final Vector<AlarmRead> alarms=new Vector<FileSender.AlarmRead>();
/**
* The listeners to notify when alarms have been sent to the alarm system.
*/
protected final Set<SlowTaskListener> listeners = Collections.synchronizedSet(new HashSet<SlowTaskListener>());
/**
* Constructor
*
* @param parent the parent component of the dialog
* @param contSvcs The ContainerServices
* @param sender The object to send alarms
* @param threadPrefixName The prefix of the name of the thread
*/
public BaseAlarmsSender(SenderPanel parent,ContainerServices contSvcs, ParallelAlarmSender sender, String threadPrefixName) {
if (parent==null) {
throw new IllegalArgumentException("The parent component can't be null");
}
if (contSvcs==null) {
throw new IllegalArgumentException("The ContainerServices can't be null");
}
if (sender==null) {
throw new IllegalArgumentException("The AlarmSender can't be null");
}
this.panel=parent;
this.contSvcs=contSvcs;
this.alarmsSender=sender;
threadFactory = new AlarmSenderThreadFactory(threadPrefixName);
}
/**
* Stop the thread to send alarms
*/
public synchronized void stopThread() {
if (alarmsSenderThread==null) {
return;
}
alarmsSenderThread.interrupt();
}
/**
* Add a not <code>null</code> listener.
*
* @param listener The listener to be notified
* @return <code>true</code> if this set did not already contain the specified element
*/
public boolean addSlowTaskListener(SlowTaskListener listener) {
if (listener==null) {
throw new IllegalArgumentException("The listener can't be null!");
}
return listeners.add(listener);
}
/**
* Remove a listener.
*
* @param listener The not <code>null</code> listener to remove
* @return <code>true</code> if this set contained the specified element
*/
public boolean removeSlowTaskListener(SlowTaskListener listener) {
if (listener==null) {
throw new IllegalArgumentException("The listener can't be null!");
}
return listeners.remove(listener);
}
/**
* Stop the thread and frees the resources
*/
public synchronized void close() {
stopThread();
}
/**
* Notify the listeners that a slow task started.
*
* @param nSteps The number of steps or <code>null</code> if undefined.
*/
protected void notifyStartTask(Integer nSteps) {
synchronized (listeners) {
for (SlowTaskListener listener: listeners) {
listener.slowTaskStarted(this,nSteps);
}
}
}
/**
* Notify the listeners that a slow task terminated.
*/
protected void notifyStopTask() {
synchronized (listeners) {
for (SlowTaskListener listener: listeners) {
listener.slowTaskFinished(this);
}
}
}
/**
* Notify the listeners that a slow task terminated.
*
* @param currentStep The actual step
*/
protected void notifyTaskProgress(int currentStep) {
synchronized (listeners) {
for (SlowTaskListener listener: listeners) {
listener.slowTaskProgress(this,currentStep);
}
}
}
/**
* Notify the listeners that the alarms have been read from the file
* or the TM/CDB
*/
protected void notifyAlarmsRead() {
synchronized (listeners) {
for (SlowTaskListener listener: listeners) {
listener.alarmsRead(this, alarms.size());
}
}
}
/**
* @return The number of alarms in memory i.e. the number of alarms
* read from the file
* @return
*/
public int size() {
return alarms.size();
}
/**
* Print on the stdout the alarms {@link InterruptedException} {@link #alarms}
*/
protected void dumpAlarms() {
System.out.println("Alarms in memory: "+alarms.size());
for (AlarmRead alarm: alarms) {
System.out.println("\t"+alarm.toString());
}
}
/**
* Send the alarms read from the file.
*
* @param active if <code>true</code> the alarms are activated,
* otherwise terminated
*/
public synchronized void sendAlarms(final boolean active) {
stopThread();
alarmsSenderThread = threadFactory.newThread(new Runnable() {
@Override
public void run() {
AlarmDescriptorType type = (active)?AlarmDescriptorType.ACTIVE:AlarmDescriptorType.TERMINATE;
int nAlarmsSent=0;
synchronized (alarms) {
notifyStartTask(alarms.size());
for (AlarmRead alarm: alarms) {
if (Thread.currentThread().isInterrupted()) {
break;
}
if (alarm.isDefaultFM()) {
// Defaults are discarded when raising/clearing all the alarms
continue;
}
try {
alarmsSender.send(alarm.triplet, type, alarm.properties);
if (nAlarmsSent%10==0) {
notifyTaskProgress(++nAlarmsSent);
}
} catch (InterruptedException ie) {
// If interrupted, terminates.
break;
}
}
}
notifyStopTask();
}
});
alarmsSenderThread.start();
}
/**
* Start sending alarms randomly.
* <P>
* The triplet of alarms are those set in the file but they are picked up randomly
* in a loop.
* The activation state (ACTIVE, TERMINATE) is also chosen randomly
*
*/
public synchronized void startSendingRandomly() {
stopThread();
alarmsSenderThread = threadFactory.newThread(new Runnable() {
@Override
public void run() {
notifyStartTask(null);
Random rnd = new Random(System.currentTimeMillis());
while (!Thread.currentThread().isInterrupted()) {
AlarmDescriptorType type = (rnd.nextBoolean()) ? AlarmDescriptorType.ACTIVE: AlarmDescriptorType.TERMINATE;
AlarmRead alarm;
AlarmRead alarmToSend;
synchronized (alarms) {
int alarmIdx = rnd.nextInt(alarms.size());
alarm = alarms.get(alarmIdx);
alarmToSend = alarm;
}
if (alarm.isDefaultFM()) {
int postFix = rnd.nextInt(maxDefaultFMSuffix);
String tripletStr = alarm.triplet.faultFamily + ","+ defaultFMNamePrefix + postFix + ","+ alarm.triplet.faultCode;
try {
alarmToSend = new AlarmRead(tripletStr, alarm.props);
} catch (Throwable t) {
contSvcs.getLogger().log(AcsLogLevel.ERROR, "Exception building an alarm from a default", t);
t.printStackTrace();
continue;
}
}
try {
alarmsSender.send(alarmToSend.triplet, type,
alarmToSend.properties);
} catch (InterruptedException e) {
break;
}
}
notifyStopTask();
}
});
alarmsSenderThread.start();
}
}