/*
* ALMA - Atacama Large Millimiter Array
* (c) European Southern Observatory, 2002
* Copyright by ESO (in the framework of the ALMA collaboration)
* and Cosylab 2002, All rights reserved
*
* 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 com.cosylab.logging.engine.ACS;
import java.lang.reflect.Constructor;
import java.util.concurrent.atomic.AtomicReference;
import org.omg.CORBA.ORB;
import si.ijs.maci.Manager;
import alma.acs.logging.AcsLogger;
import alma.acs.logging.ClientLogManager;
import alma.acs.util.stringqueue.DefaultXmlQueueFileHandlerImpl;
import alma.acs.util.stringqueue.TimestampedStringQueueFileHandler;
import com.cosylab.logging.engine.Filter;
import com.cosylab.logging.engine.Filterable;
import com.cosylab.logging.engine.FiltersVector;
import com.cosylab.logging.engine.LogEngineException;
import com.cosylab.logging.engine.RemoteAccess;
import com.cosylab.logging.engine.audience.Audience;
import com.cosylab.logging.engine.log.LogTypeHelper;
/**
* LCEngine connects to the logging NC and sends messages to the listeners.
*
* A LCEngine object can have an audience (as defined in the log_audience IDL
* module). If an audience is in use, a special set of filters will be applied.
*
* It is possible to define custom filters to apply to the incoming logs. In this
* case, only the logs that pass all the filters are sent to the listeners. The
* filters do not apply to RAW (i.e. XML) log listeners.
* The custom filters are applied after the audience filtering.
*
* There are three type of listeners supported: - ACSLogConnectionListener:
* listens events related to the connection with the logging NC and
* reportMessages - ACSRemoteLogListener: listens for LogEntries -
* ACSRemoteRawLogListener: listens for XML strings representing logs
*
* It there are no ACSRemoteLogLiestenersRegistered then the string received
* from the NC is not parsed
*
* The logs read from the NC are sent to the <code>logRetrieval</code> that,
* in turn, send them to the registered listeners.
* Audience and filtering are implemented by <code>ACSLogRetrieval</code>.
*
* @see ACSRemoteLogListener
* @see ACSRemoteRawLogListener
* @see ACSLogConnectionListener
*
*/
public class LCEngine implements Filterable {
/**
* The connection is checked every CHECK_INTERVAL seconds
*/
private final int CHECK_INTERVAL = 15;
private final AcsLogger logger;
// The boolean remember if the client was connected before
// checking for the connection (needed to understand if the
// connection has been lost or never happened)
private boolean wasConnected = false;
// Signal the thread to terminate
private AtomicReference<AccessChecker> connCheckerThread = new AtomicReference<LCEngine.AccessChecker>(null);
private volatile boolean terminateThread = false;
private RemoteAccess remoteAccess = null;
/**
* The filters to apply to the incoming logs.
*
* The filters are applied by <code>ACSLogReceiver.run()</code>
*/
private FiltersVector filters = null;
/**
* A thread used to set and initialize RemoteAccess
*
* Constructor parameters:
*
* @accessType (String) name of the RemoteAccessClass e.g. ACS class has to
* be in the package com.cosylab.logging.engine."accessType" and
* has to be named "accessType"RemoteAccess e.g.
* com.cosylab.logging.engine.ACS.ACSRemoteAccess
*/
private class AccessSetter extends Thread {
/**
* Constructor
*
*/
public AccessSetter() {
super("AccessSetter");
setDaemon(true);
}
/**
* Connect to the NC. It starts the thread to check the status of the
* connection (LCEngine.run) if it is not already alive (this last case
* can happen if the connection has been lost)
*/
public void run() {
if (listenersDispatcher==null || logRetrieval==null) {
throw new IllegalStateException("The listener and the log retrieval can't be null");
}
disconnectRA();
listenersDispatcher.publishConnecting();
listenersDispatcher.publishReport("Connecting to " + accessType
+ " remote access...");
try {
// TODO: Shouldn't we instantiate ACSRemoteAccess directly? Who gains from 'forName'?
Class[] parameters = { listenersDispatcher.getClass(), logRetrieval.getClass(), AcsLogger.class };
String raName = accessType;
if (accessType.indexOf(".") == -1)
raName = "com.cosylab.logging.engine." + accessType + "."
+ accessType + "RemoteAccess"; // com.cosylab.logging.engine.ACS.ACSRemoteAccess
Class theClass = Class.forName(raName);
Constructor ctor = theClass.getConstructor(parameters);
remoteAccess = (RemoteAccess)ctor.newInstance(listenersDispatcher,logRetrieval, logger);
remoteAccess.initialize(orb, manager);
} catch (Throwable e) {
listenersDispatcher
.publishReport("Exception occurred when initializing "
+ accessType + " remote access.");
listenersDispatcher.publishConnected(false);
System.out.println("Exception in LCEngine$AccessSetter::run(): "+ e);
e.printStackTrace();
return;
}
if (remoteAccess != null && remoteAccess.isInitialized()) {
listenersDispatcher.publishReport("Connected to " + accessType
+ " remote access.");
listenersDispatcher.publishConnected(true);
LCEngine.this.wasConnected = true;
} else {
listenersDispatcher.publishConnected(false);
}
}
}
/**
* The thread to check the status of the connection
*
* @author acaproni
*
*/
private class AccessChecker extends Thread {
/**
* Constructor
*
*/
public AccessChecker() {
super("AccessChecker");
}
/**
* The thread that 1- monitors the status of the connection 2- reconnect
* if the autoReconnect option is activated
*
* The thread is started when the connection is activated and terminated
* when disconnects from the NC.
*
*/
public void run() {
int currentSec = 0;
while (!terminateThread) {
// wait for CHECK_INTERVAL secs..
while (currentSec < CHECK_INTERVAL) {
try {
Thread.sleep(1000);
currentSec++;
if (terminateThread) {
return;
}
} catch (InterruptedException ie) {
continue;
}
}
currentSec = 0;
// Check the connection!
boolean connected = isConnected();
// publishConnected(connected);
if (wasConnected && !connected) {
listenersDispatcher.publishReport("Connection lost");
wasConnected = false;
disconnectRA();
listenersDispatcher.publishConnectionLost();
// Better otherwise it tries to reconnect every time
if (!autoReconnect) {
return; // Terminate the thread
}
}
if (autoReconnect && !connected) {
connect();
}
}
}
}
/**
* accessType, orb and manager are used to connect to the logging channel
*
* @see AccessSetter.run
*/
private String accessType = "ACS";
private ORB orb = null; // Can be null
private Manager manager = null; // Can be null
/**
* If true the engine tries to reconnect automatically
*/
private boolean autoReconnect = false;
// Dispatches messages to the listeners
private ACSListenersDispatcher listenersDispatcher = new ACSListenersDispatcher();
// The log retrieval i,e. the object receiving logs and dispatching
// to the listeners
private final ACSLogRetrieval logRetrieval;
/**
* LCEngine constructor
*
* @see #LCEngine(boolean, FiltersVector, ILogQueueFileHandler)
*/
public LCEngine() throws LogEngineException {
this(false,null,null);
}
/**
* LCEngine constructor.
*
* @param autoReconn If <code>true</code> the engine automatically reconnects
*
* @see #LCEngine(boolean, FiltersVector, ILogQueueFileHandler)
*/
public LCEngine(boolean autoReconn) throws LogEngineException {
this(autoReconn,null,null);
}
/**
* LCEngine constructor.
* <P>
* This constructor allows to define a set of filters to apply to incoming
* logs: only the logs passing the filters are sent to the listeners.
* <P>
*
* @param autoReconn
* If true the engine automatically reconnects
* @param filters
* The filters to apply to the incoming logs.
* <code>filters</code> can be null or empty.
*
* @see #LCEngine(boolean, FiltersVector, ILogQueueFileHandler)
*/
public LCEngine(boolean autoReconn, FiltersVector filters) throws LogEngineException {
this(autoReconn,filters,null);
}
/**
* Build the <code>LCEngine</code> by setting an handler for
* the files of the cache.
*
* @param cacheFileHandler The handler of the files of the cache
*
* @see ILogQueueFileHandler
* @see #LCEngine(boolean, FiltersVector, ILogQueueFileHandler)
*/
public LCEngine(TimestampedStringQueueFileHandler cacheFileHandler) throws LogEngineException {
this(false,null,cacheFileHandler);
}
/**
* <code>LCEngine</code> constructor.
*
* @param autoReconn If <code>true</code> the engine automatically reconnects
* @param cacheFileHandler The handler of the files of the cache
*
* @see ILogQueueFileHandler
* @see #LCEngine(boolean, FiltersVector, ILogQueueFileHandler)
*/
public LCEngine(boolean autoReconn, TimestampedStringQueueFileHandler cacheFileHandler)throws LogEngineException {
this(autoReconn,null,cacheFileHandler);
}
/**
* <code>LCEngine</code> constructor.
* <P>
* This constructor allows to define a set of filters to apply to incoming
* logs: only the logs passing the filters are sent to the listeners.
* <P>
*
* @param autoReconn If <code>true</code> the engine automatically reconnects
* @param filters
* The filters to apply to the incoming logs.
* <code>filters</code> can also be either <code>null</code> or empty.
* @param cacheFileHandler The handler of the files of the cache or
* <code>null</code> if do not want to register for notification
*
* @see ILogQueueFileHandler
*/
public LCEngine(
boolean autoReconn,
FiltersVector filters,
TimestampedStringQueueFileHandler cacheFileHandler) throws LogEngineException {
logger = ClientLogManager.getAcsLogManager().getLoggerForApplication("jlogEngine", false);
if (cacheFileHandler==null) {
cacheFileHandler = new DefaultXmlQueueFileHandlerImpl("Log");
}
//ClientLogManager.getAcsLogManager().suppressRemoteLogging();
logRetrieval=new ACSLogRetrieval(
listenersDispatcher,
cacheFileHandler);
autoReconnect = autoReconn;
setFilters(filters,false);
logRetrieval.start();
}
/**
* LCEngine starts an attempt to connect to the remote system. Connection is
* handled in a separate Thread
*
* @see LCEngine$AccessSetter
*/
public synchronized void connect() {
new AccessSetter().start();
if (connCheckerThread.get() == null || !connCheckerThread.get().isAlive()) {
terminateThread = false;
AccessChecker setter = new AccessChecker();
connCheckerThread.set(setter);
setter.setName("LCEngine");
setter.setDaemon(true);
setter.start();
}
}
/**
* LCEngine starts an attempt to connect to the remote system. Connection is
* handled in a separate Thread
*
* @param accessTyp
* The access type
*
* @see LCEngine$AccessSetter
*/
public void connect(String newAccessType) {
setAccessType(newAccessType);
connect();
}
/**
* LCEngine starts an attempt to connect to the remote system. Connection is
* handled in a separate Thread
*
* @param theORB
* The ORB (can be null)
* @param mgr
* The reference to the manager (can be null)
*
* @see LCEngine$AccessSetter
*/
public void connect(ORB theORB, Manager mgr) {
setConnectionParams(theORB, mgr);
connect();
}
/**
* Close the engine and free the resources.
*
* @param sync If <code>true</code> the closing is made in a synchronized way.
*/
public void close(boolean sync) {
logRetrieval.close(sync);
disconnect(sync);
}
/**
* Disconnect the engine
*/
public void disconnect() {
disconnect(true);
}
/**
* LCEngine starts an attempt to disconnect to the remote system.
*
* @param sync If <code>true</code> the closing is made in a synchronized way.
* @see LCEngine$AccessSetter
*/
public void disconnect(boolean sync) {
// Stop the thread to check the status of the connection
synchronized (this) {
terminateThread = true;
AccessChecker checker = connCheckerThread.get();
if (checker != null) {
checker.interrupt();
}
}
disconnectRA();
if (!sync) {
return;
}
// Wait for termination
while (true) {
AccessChecker checker = connCheckerThread.get();
if (checker!=null && checker.isAlive()) {
try {
Thread.sleep(250);
} catch (InterruptedException ie) {
continue;
}
} else {
break;
}
}
connCheckerThread.set(null);
}
/**
* Disconnect the remote access
*
*/
private void disconnectRA() {
if (remoteAccess != null && remoteAccess.isInitialized()) {
try {
listenersDispatcher.publishReport(
"Disconnecting from "+accessType+" remote access...");
remoteAccess.close(true);
} catch (Exception e) {
listenersDispatcher.publishReport(
"Exception occurred when destroying "+ accessType + " remote access.");
System.out.println("Exception in LCEngine$AccessDestroyer::run(): "+ e);
e.printStackTrace();
}
listenersDispatcher.publishReport("Disconnected from " + accessType + " remote access.");
}
if (remoteAccess != null) {
remoteAccess.destroy();
}
remoteAccess = null;
listenersDispatcher.publishConnected(false);
LCEngine.this.wasConnected = false;
}
/**
* Insert the method's description here. Creation date: (2/18/2002 9:58:30
* AM)
*
* @return java.lang.String
*/
public java.lang.String getAccessType() {
return accessType;
}
/**
* Insert the method's description here. Creation date: (2/18/2002 9:58:30
* AM)
*
* @param newAccessType
* java.lang.String
*/
public void setAccessType(String newAccessType) {
accessType = newAccessType;
}
/**
* Set the connection params fro this
*
* @param theORB
* The ORB. It can't be null if the manager is not null
* @param mgr
* The reference to the Manager
*/
public void setConnectionParams(ORB theORB, Manager mgr) {
orb = theORB;
manager = mgr;
}
/**
*
* @return ture if the engine is connected to the notification channel
*/
public boolean isConnected() {
if (remoteAccess == null) {
return false;
}
return remoteAccess.isConnected();
}
/**
* Suspend resume the notification of logs NOTE: When suspended the log
* discarded are lost forever
*
* @param suspended
* If true suspend the notification of the logs
*/
public void setSupended(boolean suspended) {
if (remoteAccess instanceof ACSRemoteAccess) {
((ACSRemoteAccess) remoteAccess).setSuspended(suspended);
}
}
/**
* Enable/disable the auto reconnection
*
* @param autoRec
* If true the engine tries to reconnect automatically
*/
public void enableAutoReconnection(boolean autoRec) {
autoReconnect = autoRec;
}
/**
* Pause/unpause the publishing of logs to the listener The difference
* between pause and suspended is that when the engine is suspended all the
* received logs are discarded. When it is paused, the received logs are
* cached and published when the engine will be unpaused.
*
* @param pause
*/
public void setPaused(boolean pause) {
logRetrieval.pause(pause);
}
/**
* Add a log listener
*
* @param listener
* The listener to add
*
* @see ACSRemoteLogListener
*/
public void addLogListener(ACSRemoteLogListener listener) {
listenersDispatcher.addLogListener(listener);
}
/**
* Add a RAW log listener
*
* @param listener
* The listener to add
*
* @see ACSRemoteRawLogListener
*/
public void addRawLogListener(ACSRemoteRawLogListener listener) {
listenersDispatcher.addRawLogListener(listener);
}
/**
* Add an error listener
*
* @param listener
* The error listener to add
*
* @see ACSRemoteErrorListener
*/
public void addLogErrorListener(ACSRemoteErrorListener listener) {
listenersDispatcher.addErrorListener(listener);
}
/**
* Add a connection listener
*
* @param listener
* The listener to add
*
* @see ACSLogConnectionListener
*
*/
public void addLogConnectionListener(ACSLogConnectionListener listener) {
listenersDispatcher.addLogConnectionListener(listener);
}
/**
* Set the filters to apply before sending the log entries to the listener.
* By setting a null or empty filters disable the filtering.
*
* @param newFilters
* The new filters to apply If <code>null</code> or empty the
* filtering is disabled
* @param append
* If <code>true</code> the new filters are added to the
* existing filters (if any).
*/
public void setFilters(FiltersVector newFilters, boolean append) {
if (newFilters==null || newFilters.isEmpty()) {
filters=null;
return;
}
if (filters==null) {
filters=new FiltersVector();
}
if (!append) {
filters.clear();
}
for (int t=0; t<newFilters.size(); t++) {
filters.addFilter(newFilters.get(t), newFilters.isActive(t));
}
logRetrieval.setFilters(filters);
}
/**
* Set the discard level for filtering.
* <P>
* <I>Note</I>: if the dynamic change of the discard
* level depending on the memory usage has been
* activated, then the discard level effectively
* used by the engine might be greater then
* <code>newDiscardlevel</code>.
*
* @param newDiscardlevel The discard level
* Not applied if <code>null</code>.
* @see {@link ACSLogRetrieval}
*
*/
public void setDiscardLevel(LogTypeHelper newDiscardLevel) {
logRetrieval.setDiscardLevel(newDiscardLevel);
}
/**
* Set the audience for the engine.
* If an audience is defined, a special set of filters is applied.
*
* @param newAudience The not <code>null</code >audience
*/
public void setAudience(Audience newAudience) {
if (newAudience==null) {
throw new IllegalArgumentException("The audience can't be null");
}
logRetrieval.setAudience(newAudience);
}
/**
* Add a new filters to the vector of filters to apply to the incoming logs
* before sending to the listeners.
* <P>
* The filter is applied as active.
*
* @param filter
* The not null filter to add
*/
public void addFilter(Filter filter) {
if (filter == null) {
throw new IllegalArgumentException("The filter can't be null");
}
if (filters == null) {
filters = new FiltersVector();
}
filters.addFilter(filter, true);
logRetrieval.setFilters(filters);
}
/**
* Remove all the filters.
*
* After calling this method, all the received logs are forwarded to the
* listeners.
*/
public void clearFilters() {
filters = null;
logRetrieval.setFilters(null);
}
/**
* Return the filters to filter the incoming logs before sending to the
* listeners
*
* @return The filters (can be <code>null</code>)
*/
public FiltersVector getFilters() {
return filters;
}
/**
* Return the discard level in use by the engine.
* <P>
* <I>Note</I>: the actual discard level may or may not be the same
* discard level set by the user.
* They differ if the user enabled the dynamic change of
* the discard level depending on the available memory .
*
* @return The actual discard level (can be <code>null</code>)
*
* @see {@link ACSLogRetrieval.getDiscardlevel()}, {@link LogMatcher.getActualDiscardLevel()}
* @see <code>getDiscardLevel()</code>
*/
public LogTypeHelper getActualDiscardLevel() {
return logRetrieval.getActualDiscardLevel();
}
/**
* Return the discard level set by the user.
* <P>
* <I>Note</I>: the discard level in use can be different if the
* dynamic change of the discard level depending
* on the available memory in the engine has been
* activated.
*
* @return The discard level (can be <code>null</code>)
*
* @see {@link ACSLogRetrieval.getDiscardlevel()}, {@link LogMatcher.getActualDiscardLevel()}
* @see <code>getActualDiscardLevel()</code>
*/
public LogTypeHelper getDiscardLevel() {
return logRetrieval.getDiscardLevel();
}
/**
*
* @return The discard level (can be <code>null</code>)
*/
public Audience getAudience() {
return logRetrieval.getAudience();
}
/**
* Return <code>true</code> if the engine is applying filters.
* If a filter is in place but not enabled then it is ignored.
*
* @return <code>true</code> if there are filters active in the engine
* @see FiltersVector.hasActiveFilters()
*/
public boolean isFiltered() {
if (filters==null) {
return false;
} else {
return filters.hasActiveFilters();
}
}
/**
*
* @return A description of the active filters
* @see FiltersVector.getFilterString()
*/
public String getFiltersString() {
if (filters==null) {
return "Not filtered";
} else {
return filters.getFilterString();
}
}
/**
* Set the max number of logs per second to accept from the
* <code>RemoteAccess</code>, typically the logging NC.
* <P>
* All the logs arriving after the max number has been reached will be discarded,
* regardless of their level.
* <P>
* See {@link ACSLogRetrieval} for further details.
*
* @param rate The max number of logs per second to accept
*/
public void setMaxInputRate(int rate) {
logRetrieval.setMaxInputRate(rate);
}
/**
* @return The actual max input rate
*
* See {@link ACSLogRetrieval} for further details.
*/
public int getMaxInputRate() {
return logRetrieval.getMaxInputRate();
}
/**
*
* @return The actual input rate
*
* See {@link ACSLogRetrieval} for further details.
*/
public int getActualInputRate() {
return logRetrieval.getInputRate();
}
/**
* Set the max number of logs to publish to listeners.
* <P>
* When this number has been reached, no more logs are sent to the
* listeners.
* <P>
* See {@link ACSLogRetrieval} for further details.
*
* @param rate The max number of logs per second to accept
*/
public void setMaxOutputRate(int rate) {
logRetrieval.setMaxOutputRate(rate);
}
/**
* @return The actual max input rate
*
* See {@link ACSLogRetrieval} for further details.
*/
public int getMaxOutputRate() {
return logRetrieval.getMaxOutputRate();
}
/**
*
* @return The actual input rate
*
* See {@link ACSLogRetrieval} for further details.
*/
public int getActualOutputRate() {
return logRetrieval.getOutputRate();
}
/**
* Return the number of logs waiting in the cache i.e. the logs
* received from the <code>RemoteAcess</code> and not yet sent to
* the listeners.
* @return the number of logs waiting in the cache
*/
public int waitingLogsNumber() {
return logRetrieval.size();
}
/**
* Enable or disable the dynamic change of the discard level
* depending on the amount of available memory.
*
* @param threashold The discard level is increased when the available
* memory for the application is less then the <code>threshold</code>
* (in bytes).
* <code>Integer.MAX_VALUE</code> disables this feature.
* @param damping The damping factor is used to avoid oscillations
* The discard level is decreased when the free memory is
* is greater then the <code>threshold</code> plus the <code>dumping</code>.
* @param interval The time (in seconds) between two adjustments of the
* dynamic discard level.
* <code>interval</code> defaults to <code>10</code>.
*
* @see {@link ACSLogRetrieval}
*/
public void enableDynamicDiscarding(int threshold, int damping, int interval) {
logRetrieval.enableDynamicDiscarding(threshold, damping, interval);
}
}