/*
ALMA - Atacama Large Millimiter Array
* Copyright (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.alarmsystem.dump;
import java.sql.Timestamp;
import java.util.Date;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;
import cern.laser.source.alarmsysteminterface.FaultState;
import alma.acs.component.client.AdvancedComponentClient;
import alma.acs.component.client.ComponentClient;
import alma.acs.logging.ClientLogManager;
import alma.acs.shutdown.ShutdownHookBase;
import alma.acs.util.IsoDateFormat;
import alma.alarmsystem.clients.SourceClient;
import alma.alarmsystem.clients.source.SourceListener;
/**
* The base class of the alarm dumper. It groups common code for the dumpers that write
* XML alarms, from sources or categories, in the stdout.
* <P>
* The pattern for using a dumper is:
* <OL>
* <LI>Instantiate the dumper that extends this class
* <LI>call {@link #receiveAlarms()}
* </OL>call {@link #tearDown()}
* <P>
* {@link #receiveAlarms()} blocks until the user presses CTRL-C a signal is received
* or {@link #tearDown()} is called.<BR>
* {@link #receiveAlarms(long, TimeUnit)} can be used instead of {@link #receiveAlarms()}
* if the receiving has to terminate after the specified timeout.
* <P>
* {@link #tearDown()} must be called before terminating to cleanly exit.
*
* @author acaproni
* @since ACS-10.1
*/
public abstract class AlarmDumperBase extends AdvancedComponentClient {
/**
* Detects when the user issued a CTRL+C and disconnects the {@link SourceClient}
* and the {@link AdvancedComponentClient} before exiting.
*
* @author acaproni
*
*/
private final class ShutdownHook extends ShutdownHookBase {
/**
* Constructor
*/
public ShutdownHook() {
super(AlarmDumperBase.this.m_logger,AlarmDumperBase.class.getName());
}
/**
* Close the {@link SourceClient} and tearDown the {@link AdvancedComponentClient}
*/
@Override
protected void interruptDetected() {
tearDown();
}
@Override
protected void regularTermination() {
// Inhibit logging generated by super class.
}
}
/**
* We use this synchronization aid to signal that the user pressed CTRL-C so that
* the object stops receiving XML strings.
*/
private final CountDownLatch m_latch = new CountDownLatch(1);
/**
* <code>true</code> if the dumper has been closed
*/
private final AtomicBoolean m_closed = new AtomicBoolean(false);
/**
* The formatter for the timestamp
*/
private final IsoDateFormat m_dateFormat = new IsoDateFormat();
/**
* Constructor
*
* @param theLogger The logger
* @param mangerLoc The CORBA loc of the manager
* @param clientname The name of the client
* @throws Exception if an error happens instantiating the {@link AdvancedComponentClient}
*/
public AlarmDumperBase(Logger theLogger, String mangerLoc,String clientname ) throws Exception {
super(theLogger,mangerLoc,clientname);
}
/**
* Connects to the NC and start receiving alarms.
* <P>
* This must be implemented to connect to the right NC.
* If the connection failed, {@link #startReceivingAlarms()} must thrown an exception
*/
public abstract void startReceivingAlarms() throws Exception ;
/**
* Connect the client
*
* This method never stops.
*
* @throws Exception In case of error connecting the {@link SourceClient}
*/
public void receiveAlarms() throws Exception {
if (m_closed.get()) {
m_logger.log(Level.SEVERE, "AlarmSourceDump is closed.");
}
startReceivingAlarms();
m_latch.await();
}
/**
* Connect the client until the timeout elapses or the user presses CTRL-C
*
* This method never when the timeout elapse or the user presses CTRL-C
*
* @throws Exception In case of error connecting the {@link SourceClient}
*/
public void receiveAlarms(long timeout, TimeUnit timeUnit) throws Exception {
if (m_closed.get()) {
m_logger.log(Level.SEVERE, "AlarmSourceDump is closed.");
}
if (timeUnit==null) {
throw new IllegalArgumentException("Invalid NULL TimeUnit");
}
startReceivingAlarms();
m_latch.await(timeout,timeUnit);
}
/**
* Close the resources of the alarm client
*/
public abstract void close() throws Exception ;
/**
* Cleanly tear down
*/
public synchronized void tearDown() {
if (m_closed.getAndSet(true)) {
// Already called
//
// tearDown is, in fact, called twice:the shutdown hook and in by main
return;
}
// Stop receiving XMLs
m_latch.countDown();
// Close client
try {
close();
} catch (Throwable t) {
// Nothing to do but we log a message..
m_logger.log(Level.SEVERE, "Error shutting closing the alarm client", t);
}
// Close the component client
try {
super.tearDown();
} catch (Throwable t) {
// Nothing to do but we log a message..
m_logger.log(Level.SEVERE, "Error shutting down the component client", t);
}
}
/**
* {@link AlarmDumperBase} registers its own shutdown hook in fact what is provided
* by {@link ComponentClient} logs a few warnings if the process is interrupted
* but in this case the situation is absolutely normal.
*/
@Override
protected void registerShutdownHook() {
m_shutdownHook = new ShutdownHook();
Runtime.getRuntime().addShutdownHook(m_shutdownHook);
}
/**
* Return the timestamp in ISO format.
* @param timestamp
* @return
*/
public String formatTimestamp(Timestamp timestamp) {
if (timestamp==null) {
throw new IllegalArgumentException("The timestamp can't be null");
}
synchronized (m_dateFormat) {
return m_dateFormat.format(new Date(timestamp.getTime()));
}
}
/**
* This block of code avoid code replication.
*
* @param args Command line args
* @param isSourceClient <code>true</code> if a source client must be instantiated,
* <code>false</code> for category client
* @param timeout If greater then <code>0</code> the process waits until the timeout elapses
* @param timeUnit The TimeUnit for the timeout (must be not <code>null</code> if the timeout is greater then <code>0</code>)
*/
public static void clientRunner(String[] args, boolean isSourceClient, long timeout, TimeUnit timeUnit) {
if (timeout>0 && timeUnit==null) {
throw new IllegalArgumentException("Invalid parameters for timeout");
}
Logger logger = ClientLogManager.getAcsLogManager().getLoggerForApplication("AlarmSourceDump",true);
String managerLoc = System.getProperty("ACS.manager");
if (managerLoc == null) {
System.err.println("Java property 'ACS.manager' must be set to the corbaloc of the ACS manager!");
System.exit(-1);
}
AlarmDumperBase dumper=null;
try {
if (isSourceClient) {
dumper = new AlarmSourceDumper(logger, managerLoc);
} else {
dumper = new AlarmCategoryDumper(logger,managerLoc);
}
} catch (Throwable t) {
System.err.println("Exception caught while creating the AlarmSourceDump: "+t.getMessage());
t.printStackTrace(System.err);
System.exit(-1);
}
try {
if (timeout>0) {
dumper.receiveAlarms(timeout,timeUnit);
} else {
// No timeout
dumper.receiveAlarms();
}
} catch (Throwable t) {
logger.log(Level.SEVERE, "Error connecting the alarm source client", t);
}
dumper.tearDown();
}
}