package com.threatconnect.sdk.log;
import com.threatconnect.app.apps.AppConfig;
import com.threatconnect.sdk.app.LoggerUtil;
import com.threatconnect.sdk.app.SdkAppConfig;
import com.threatconnect.sdk.client.writer.LogWriterAdapter;
import com.threatconnect.sdk.config.Configuration;
import com.threatconnect.sdk.conn.Connection;
import java.io.IOException;
import java.util.AbstractQueue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* This singleton maintains the logic needed for recording and reporting log entries to the server.
* 2 queues which are used to accomplish this task. The first queue is used to store log entries
* until a threshold is reached. Once this occurs, a task is created which will be responsible for
* making a batch API call to the server with all of the log entries. The second queue is used to
* track the actual executing api calls. In the event that multiple batch requests are created
* before the previous can finish executing, this queue helps maintain the order of all log entries
* as well as track actively executing api calls.
*
* @author Greg Marut
*/
public class ServerLogger
{
// holds the default threshold for when the log entries will be flushed to the server as a batch
public static final int DEFAULT_BATCH_THRESHOLD = 100;
// holds the instance of this singleton
private static ServerLogger instance;
private static final Object lock = new Object();
// holds the executor service for flushing the logs to the server
private final ExecutorService taskExecutorService;
// holds the queue of server log entries that are pending submit
private final AbstractQueue<LogEntry> logEntryQueue;
private final AbstractQueue<LogWriterTask> logWriterTasks;
// determines whether or not server logging is enabled
private volatile boolean enabled;
// holds the configuration object for connecting to the api
private Configuration configuration;
// holds the batch threshold for how many log entries are needed before they are flushed to the
// server
private int batchLogEntryThreshold;
private ServerLogger()
{
logEntryQueue = new ConcurrentLinkedQueue<LogEntry>();
logWriterTasks = new ConcurrentLinkedQueue<LogWriterTask>();
taskExecutorService = Executors.newSingleThreadExecutor();
setBatchLogEntryThreshold(DEFAULT_BATCH_THRESHOLD);
setConfiguration(createConfiguration());
setEnabled(true);
}
public void addLogEntry(final LogEntry logEntry)
{
// ensure that server logging is enabled
if (isEnabled())
{
// adds a log entry to the queue
logEntryQueue.add(logEntry);
// check to see if the log entries need to be flushed
flushToServerIfNeeded();
}
}
public int getBatchLogEntryThreshold()
{
return batchLogEntryThreshold;
}
public void setBatchLogEntryThreshold(int batchLogEntryThreshold)
{
this.batchLogEntryThreshold = batchLogEntryThreshold;
}
/**
* Writes all remaining log entries to the server. This method blocks until all entries have
* been uploaded to the server
*/
public void flushToServer()
{
// ensure that server logging is enabled
if (isEnabled())
{
// prepare any remaining log entries from the queue
prepareLogWriterTask();
// acquire a thread lock on the queue
synchronized (logWriterTasks)
{
// while there are more tasks to write
while (!logWriterTasks.isEmpty())
{
// execute the next pending task
logWriterTasks.poll().run();
}
}
}
}
/**
* First checks to see if the log entry queue has reached the required threshold, if so, it is
* flushed to the server
*/
private void flushToServerIfNeeded()
{
// ensure that server logging is enabled
if (isEnabled())
{
// check to see if the size of the queue exceeds the threshold
if (logEntryQueue.size() >= getBatchLogEntryThreshold())
{
// run this inside of a new thread
taskExecutorService.execute(new Runnable()
{
@Override
public void run()
{
flushToServer();
}
});
}
}
}
/**
* Converts the queue of log entries into a task that is ready to be sent to the server
*/
private void prepareLogWriterTask()
{
// ensure that server logging is enabled
if (isEnabled())
{
// acquire a thread lock on the queue
synchronized (logEntryQueue)
{
// convert the queue to an array
final LogEntry[] logEntryArray = logEntryQueue.toArray(new LogEntry[logEntryQueue.size()]);
// make sure there are log entries
if (logEntryArray.length > 0)
{
// clear the queue
logEntryQueue.clear();
try
{
// create the runnable task that will send the batch of log entries to the
// server
LogWriterTask task = new LogWriterTask(new LogWriterAdapter(createConnection()), logEntryArray);
logWriterTasks.add(task);
}
catch (IOException e)
{
LoggerUtil.logErr(e, e.getMessage());
}
}
}
}
}
public Configuration getConfiguration()
{
return configuration;
}
public void setConfiguration(Configuration configuration)
{
// make sure the configuration object is not null
if (null == configuration)
{
throw new IllegalArgumentException("configuration cannot be null");
}
this.configuration = configuration;
}
private Connection createConnection() throws IOException
{
return new Connection(getConfiguration());
}
private Configuration createConfiguration()
{
// create the configuration for the threatconnect server
AppConfig appConfig = SdkAppConfig.getInstance();
Configuration configuration = new Configuration(appConfig.getTcApiPath(), appConfig.getTcApiAccessID(),
appConfig.getTcApiUserSecretKey(), appConfig.getApiDefaultOrg(), appConfig.getTcToken(),
appConfig.getTcTokenExpires());
return configuration;
}
public boolean isEnabled()
{
return enabled;
}
public void setEnabled(boolean enabled)
{
this.enabled = enabled;
}
public static ServerLogger getInstance()
{
// check to see if the instance is null
if (null == instance)
{
// acquire a lock on the lock object for thread synchronization
synchronized (lock)
{
// now that a lock is in place, check again to see if the instance is still null
if (null == instance)
{
// create the new instance
instance = new ServerLogger();
}
}
}
return instance;
}
}