spade.storage.CDM.java Source code

Java tutorial

Introduction

Here is the source code for spade.storage.CDM.java

Source

/*
 --------------------------------------------------------------------------------
 SPADE - Support for Provenance Auditing in Distributed Environments.
 Copyright (C) 2015 SRI International
    
 This program is free software: you can redistribute it and/or
 modify it under the terms of the GNU General Public License as
 published by the Free Software Foundation, either version 3 of the
 License, or (at your option) any later version.
    
 This program 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
 General Public License for more details.
    
 You should have received a copy of the GNU General Public License
 along with this program. If not, see <http://www.gnu.org/licenses/>.
 --------------------------------------------------------------------------------
 */
package spade.storage;

import java.nio.ByteBuffer;
import java.util.AbstractMap.SimpleEntry;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.apache.avro.generic.GenericContainer;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.kafka.clients.CommonClientConfigs;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.common.config.SslConfigs;

import com.bbn.tc.schema.avro.cdm19.AbstractObject;
import com.bbn.tc.schema.avro.cdm19.EndMarker;
import com.bbn.tc.schema.avro.cdm19.Event;
import com.bbn.tc.schema.avro.cdm19.EventType;
import com.bbn.tc.schema.avro.cdm19.FileObject;
import com.bbn.tc.schema.avro.cdm19.FileObjectType;
import com.bbn.tc.schema.avro.cdm19.Host;
import com.bbn.tc.schema.avro.cdm19.HostIdentifier;
import com.bbn.tc.schema.avro.cdm19.HostType;
import com.bbn.tc.schema.avro.cdm19.InstrumentationSource;
import com.bbn.tc.schema.avro.cdm19.Interface;
import com.bbn.tc.schema.avro.cdm19.IpcObject;
import com.bbn.tc.schema.avro.cdm19.IpcObjectType;
import com.bbn.tc.schema.avro.cdm19.MemoryObject;
import com.bbn.tc.schema.avro.cdm19.NetFlowObject;
import com.bbn.tc.schema.avro.cdm19.Principal;
import com.bbn.tc.schema.avro.cdm19.PrincipalType;
import com.bbn.tc.schema.avro.cdm19.RecordType;
import com.bbn.tc.schema.avro.cdm19.SHORT;
import com.bbn.tc.schema.avro.cdm19.SrcSinkObject;
import com.bbn.tc.schema.avro.cdm19.SrcSinkType;
import com.bbn.tc.schema.avro.cdm19.Subject;
import com.bbn.tc.schema.avro.cdm19.SubjectType;
import com.bbn.tc.schema.avro.cdm19.TCCDMDatum;
import com.bbn.tc.schema.avro.cdm19.TimeMarker;
import com.bbn.tc.schema.avro.cdm19.UUID;
import com.bbn.tc.schema.avro.cdm19.UnitDependency;
import com.bbn.tc.schema.serialization.AvroConfig;

import spade.core.AbstractEdge;
import spade.core.AbstractVertex;
import spade.core.Settings;
import spade.reporter.Audit;
import spade.reporter.audit.OPMConstants;
import spade.utility.CommonFunctions;
import spade.utility.FileUtility;
import spade.utility.HostInfo;
import spade.vertex.opm.Artifact;
import spade.vertex.prov.Agent;

/**
 * A storage implementation that serializes and sends to kafka.
 *
 * We assume the elements (vertices and edges) received are in the OPM syntax, SPADE's native format. The TC Common
 * Data Model (CDM) includes a properties field in all model elements for including aribitrary key-value
 * pairs for SPADE element annotations that do not directly map to the CDM. NOTE: For these items, we use the Prov
 * element annotation key as the CDM properties' key, even though the input from SPADE's reporter is in OPM syntax.
 *
 * We also assume that when an OPM/PROV edge is received, we can map it to an event and edges to other
 * entity records that have already been serialized and published to Kafka.
 * 
 * How are events handled:
 * 
 * It is assumed that all edges for the same event are received back to back. All edges for the same event id and time
 * are buffered until all the edges for the current event are received. Once received those edges are processed together
 * to create the CDM event. In some special cases like setuid, update, and etc the group of edges received for a single 
 * event need to be processed individually and that is treated like a special case. All the special cases should be in
 * the function: {@link #processEdgesWrapper(List) processEdgesWrapper}
 *
 * @author Armando Caro
 * @author Hassaan Irshad
 * @author Ashish Gehani
 */
public class CDM extends Kafka {

    private final Logger logger = Logger.getLogger(CDM.class.getName());

    /**
     * Tracking counts of different kinds of objects being published/written
     */
    private Map<String, Long> stats = new HashMap<String, Long>();
    /**
     * Array of integers for an Agent as in Audit OPM model
     */
    private final String[] agentAnnotations = { OPMConstants.AGENT_UID, OPMConstants.AGENT_EUID,
            OPMConstants.AGENT_SUID, OPMConstants.AGENT_FSUID, OPMConstants.AGENT_GID, OPMConstants.AGENT_EGID,
            OPMConstants.AGENT_SGID, OPMConstants.AGENT_FSGID };
    /**
     * Flag whose value is set from arguments to decide whether to output hashcode and hex or raw bytes
     */
    private boolean hexUUIDs = false;

    /**
     * Flag to tell whether to get host info from OS or not.
     * If true then host info gotten from OS, host info saved to output file for class HostInfo and this host info
     * published.
     * If false then host info read from output file for class HostInfo and published.
     */
    private boolean createHostConfig = true;
    /**
     * A map used to keep track of:
     * 1) To keep track of parent subject UUIDs, equivalent to ppid
     */
    private final Map<String, UUID> pidSubjectUUID = new HashMap<String, UUID>();
    /**
     * To keep track of principals published so that we don't duplicate them
     */
    private final Set<UUID> publishedPrincipals = new HashSet<UUID>();
    /**
     * To keep track of the last time and event id. All edges with the same time and event id are
     * collected first and the edges are processes as one event
     */
    private SimpleEntry<String, String> lastTimeEventId = null;
    /**
     * List of currently unprocessed edges i.e. all edges for an event haven't been received
     */
    private List<Object> currentVerticesAndEdges = new ArrayList<Object>();
    /**
     * A map from the Set of edges needed for each event to complete.
     */
    private Map<Set<TypeOperation>, EventType> rulesToEventType = new HashMap<Set<TypeOperation>, EventType>();

    private UUID hostUUID = null;
    private String hostUUIDHex = null;

    private int sessionNumber = 0; // default zero

    private boolean useSsl = true;
    private String securityProtocol, trustStoreLocation, trustStorePassword, keyStoreLocation, keyStorePassword,
            keyPassword;

    /**
     * Rules from OPM edges types and operations to event types in CDM
     */
    private void populateEventRules() {
        rulesToEventType.put(getSet(new TypeOperation(OPMConstants.WAS_TRIGGERED_BY, OPMConstants.OPERATION_EXIT)),
                EventType.EVENT_EXIT);
        rulesToEventType.put(getSet(new TypeOperation(OPMConstants.WAS_TRIGGERED_BY, OPMConstants.OPERATION_FORK)),
                EventType.EVENT_FORK);
        rulesToEventType.put(getSet(new TypeOperation(OPMConstants.WAS_TRIGGERED_BY, OPMConstants.OPERATION_CLONE)),
                EventType.EVENT_CLONE);
        rulesToEventType.put(
                getSet(new TypeOperation(OPMConstants.WAS_TRIGGERED_BY, OPMConstants.OPERATION_EXECVE)),
                EventType.EVENT_EXECUTE);
        rulesToEventType.put(
                getSet(new TypeOperation(OPMConstants.WAS_TRIGGERED_BY, OPMConstants.OPERATION_SETUID)),
                EventType.EVENT_CHANGE_PRINCIPAL);
        rulesToEventType.put(
                getSet(new TypeOperation(OPMConstants.WAS_TRIGGERED_BY, OPMConstants.OPERATION_UPDATE)),
                EventType.EVENT_CHANGE_PRINCIPAL);
        rulesToEventType.put(
                getSet(new TypeOperation(OPMConstants.WAS_TRIGGERED_BY, OPMConstants.OPERATION_SETGID)),
                EventType.EVENT_CHANGE_PRINCIPAL);
        rulesToEventType.put(getSet(new TypeOperation(OPMConstants.WAS_TRIGGERED_BY, OPMConstants.OPERATION_UNIT)),
                EventType.EVENT_UNIT);
        rulesToEventType.put(getSet(new TypeOperation(OPMConstants.WAS_GENERATED_BY, OPMConstants.OPERATION_CLOSE)),
                EventType.EVENT_CLOSE);
        rulesToEventType.put(
                getSet(new TypeOperation(OPMConstants.WAS_GENERATED_BY, OPMConstants.OPERATION_UNLINK)),
                EventType.EVENT_UNLINK);
        rulesToEventType.put(getSet(new TypeOperation(OPMConstants.WAS_GENERATED_BY, OPMConstants.OPERATION_OPEN)),
                EventType.EVENT_OPEN);
        rulesToEventType.put(
                getSet(new TypeOperation(OPMConstants.WAS_GENERATED_BY, OPMConstants.OPERATION_CREATE)),
                EventType.EVENT_CREATE_OBJECT);
        rulesToEventType.put(getSet(new TypeOperation(OPMConstants.WAS_GENERATED_BY, OPMConstants.OPERATION_WRITE)),
                EventType.EVENT_WRITE);
        rulesToEventType.put(getSet(new TypeOperation(OPMConstants.WAS_GENERATED_BY, OPMConstants.OPERATION_SEND)),
                EventType.EVENT_SENDMSG);
        rulesToEventType.put(
                getSet(new TypeOperation(OPMConstants.WAS_GENERATED_BY, OPMConstants.OPERATION_MPROTECT)),
                EventType.EVENT_MPROTECT);
        rulesToEventType.put(
                getSet(new TypeOperation(OPMConstants.WAS_GENERATED_BY, OPMConstants.OPERATION_CONNECT)),
                EventType.EVENT_CONNECT);
        rulesToEventType.put(
                getSet(new TypeOperation(OPMConstants.WAS_GENERATED_BY, OPMConstants.OPERATION_TRUNCATE)),
                EventType.EVENT_TRUNCATE);
        rulesToEventType.put(getSet(new TypeOperation(OPMConstants.WAS_GENERATED_BY, OPMConstants.OPERATION_CHMOD)),
                EventType.EVENT_MODIFY_FILE_ATTRIBUTES);
        rulesToEventType.put(getSet(new TypeOperation(OPMConstants.USED, OPMConstants.OPERATION_CREATE)),
                EventType.EVENT_CREATE_OBJECT);
        rulesToEventType.put(getSet(new TypeOperation(OPMConstants.USED, OPMConstants.OPERATION_CLOSE)),
                EventType.EVENT_CLOSE);
        rulesToEventType.put(getSet(new TypeOperation(OPMConstants.USED, OPMConstants.OPERATION_LOAD)),
                EventType.EVENT_LOADLIBRARY);
        rulesToEventType.put(getSet(new TypeOperation(OPMConstants.USED, OPMConstants.OPERATION_OPEN)),
                EventType.EVENT_OPEN);
        rulesToEventType.put(getSet(new TypeOperation(OPMConstants.USED, OPMConstants.OPERATION_READ)),
                EventType.EVENT_READ);
        rulesToEventType.put(getSet(new TypeOperation(OPMConstants.USED, OPMConstants.OPERATION_RECV)),
                EventType.EVENT_RECVMSG);
        rulesToEventType.put(getSet(new TypeOperation(OPMConstants.USED, OPMConstants.OPERATION_ACCEPT)),
                EventType.EVENT_ACCEPT);
        rulesToEventType.put(
                getSet(new TypeOperation(OPMConstants.WAS_GENERATED_BY,
                        OPMConstants.buildOperation(OPMConstants.OPERATION_MMAP, OPMConstants.OPERATION_WRITE))),
                EventType.EVENT_MMAP);
        rulesToEventType.put(getSet(new TypeOperation(OPMConstants.WAS_DERIVED_FROM, OPMConstants.OPERATION_TEE),
                new TypeOperation(OPMConstants.WAS_GENERATED_BY,
                        OPMConstants.buildOperation(OPMConstants.OPERATION_TEE, OPMConstants.OPERATION_WRITE)),
                new TypeOperation(OPMConstants.USED,
                        OPMConstants.buildOperation(OPMConstants.OPERATION_TEE, OPMConstants.OPERATION_READ))),
                EventType.EVENT_OTHER); // tee
        rulesToEventType.put(
                getSet(new TypeOperation(OPMConstants.WAS_DERIVED_FROM, OPMConstants.OPERATION_SPLICE),
                        new TypeOperation(OPMConstants.WAS_GENERATED_BY,
                                OPMConstants.buildOperation(OPMConstants.OPERATION_SPLICE,
                                        OPMConstants.OPERATION_WRITE)),
                        new TypeOperation(OPMConstants.USED, OPMConstants
                                .buildOperation(OPMConstants.OPERATION_SPLICE, OPMConstants.OPERATION_READ))),
                EventType.EVENT_OTHER); // splice
        rulesToEventType.put(
                getSet(new TypeOperation(OPMConstants.WAS_GENERATED_BY, OPMConstants.OPERATION_VMSPLICE)),
                EventType.EVENT_OTHER); // vmsplice
        rulesToEventType.put(getSet(new TypeOperation(OPMConstants.USED, OPMConstants.OPERATION_INIT_MODULE)),
                EventType.EVENT_OTHER); // init_module
        rulesToEventType.put(getSet(new TypeOperation(OPMConstants.USED, OPMConstants.OPERATION_FINIT_MODULE)),
                EventType.EVENT_OTHER); // finit_module
        rulesToEventType.put(
                getSet(new TypeOperation(OPMConstants.WAS_DERIVED_FROM, OPMConstants.OPERATION_MMAP),
                        new TypeOperation(OPMConstants.WAS_GENERATED_BY,
                                OPMConstants.buildOperation(OPMConstants.OPERATION_MMAP,
                                        OPMConstants.OPERATION_WRITE)),
                        new TypeOperation(OPMConstants.USED, OPMConstants
                                .buildOperation(OPMConstants.OPERATION_MMAP, OPMConstants.OPERATION_READ))),
                EventType.EVENT_MMAP);
        rulesToEventType.put(
                getSet(new TypeOperation(OPMConstants.WAS_DERIVED_FROM, OPMConstants.OPERATION_RENAME),
                        new TypeOperation(OPMConstants.WAS_GENERATED_BY,
                                OPMConstants.buildOperation(OPMConstants.OPERATION_RENAME,
                                        OPMConstants.OPERATION_WRITE)),
                        new TypeOperation(OPMConstants.USED, OPMConstants
                                .buildOperation(OPMConstants.OPERATION_RENAME, OPMConstants.OPERATION_READ))),
                EventType.EVENT_RENAME);
        rulesToEventType.put(
                getSet(new TypeOperation(OPMConstants.WAS_DERIVED_FROM, OPMConstants.OPERATION_LINK),
                        new TypeOperation(OPMConstants.WAS_GENERATED_BY,
                                OPMConstants.buildOperation(OPMConstants.OPERATION_LINK,
                                        OPMConstants.OPERATION_WRITE)),
                        new TypeOperation(OPMConstants.USED, OPMConstants
                                .buildOperation(OPMConstants.OPERATION_LINK, OPMConstants.OPERATION_READ))),
                EventType.EVENT_LINK);
    }

    private Set<TypeOperation> getSet(TypeOperation... typeOperations) {
        Set<TypeOperation> set = new HashSet<TypeOperation>();
        for (TypeOperation typeOperation : typeOperations) {
            set.add(typeOperation);
        }
        return set;
    }

    private void publishEvent(EventType eventType, AbstractEdge edge, AbstractVertex actingProcess,
            AbstractVertex actedUpon1, AbstractVertex actedUpon2) {

        if (eventType == null || edge == null || actingProcess == null || actedUpon1 == null) {
            logger.log(Level.WARNING,
                    "Missing arguments: eventType={0}, " + "edge={1}, actingProcess={2}, actedUpon1={3}",
                    new Object[] { eventType, edge, actingProcess, actedUpon1 });
        } else {
            InstrumentationSource source = getInstrumentationSource(edge.getAnnotation(OPMConstants.SOURCE));

            UUID uuid = getUuid(edge);
            Long sequence = CommonFunctions.parseLong(edge.getAnnotation(OPMConstants.EDGE_EVENT_ID), 0L);
            Integer threadId = CommonFunctions.parseInt(actingProcess.getAnnotation(OPMConstants.PROCESS_PID),
                    null);
            UUID subjectUUID = getUuid(actingProcess);
            UUID predicateObjectUUID = getUuid(actedUpon1);
            String predicateObjectPath = actedUpon1 != null ? actedUpon1.getAnnotation(OPMConstants.ARTIFACT_PATH)
                    : null;
            UUID predicateObject2UUID = getUuid(actedUpon2);
            String predicateObject2Path = actedUpon2 != null ? actedUpon2.getAnnotation(OPMConstants.ARTIFACT_PATH)
                    : null;
            Long timestampNanos = convertTimeToNanoseconds(sequence, edge.getAnnotation(OPMConstants.EDGE_TIME),
                    0L);
            Long size = CommonFunctions.parseLong(edge.getAnnotation(OPMConstants.EDGE_SIZE), null);
            Long location = CommonFunctions.parseLong(edge.getAnnotation(OPMConstants.EDGE_OFFSET), null);

            // validation of mandatory values
            if (uuid == null || threadId == null || subjectUUID == null || predicateObjectUUID == null
                    || source == null) {
                logger.log(Level.WARNING, "NULL arguments for event: "
                        + "EdgeUUID={0}, threadId={1}, subjectUUID={2}, predicateObjectUUID={3}, " + "source={4}",
                        new Object[] { uuid, threadId, subjectUUID, predicateObjectUUID, source });
            } else {

                // + Adding all annotations to the properties map that have not been added already 
                // directly to the event
                Map<CharSequence, CharSequence> properties = new HashMap<CharSequence, CharSequence>();
                for (String key : edge.getAnnotations().keySet()) {
                    if (!key.equals(OPMConstants.EDGE_EVENT_ID) && !key.equals(OPMConstants.EDGE_TIME)
                            && !key.equals(OPMConstants.SOURCE) && !key.equals(OPMConstants.EDGE_PID)
                            && !key.equals(OPMConstants.EDGE_OPERATION) && !key.equals(OPMConstants.EDGE_SIZE)
                            && !key.equals(OPMConstants.TYPE) && !key.equals(OPMConstants.EDGE_OFFSET)) {
                        properties.put(key, edge.getAnnotation(key));
                    }
                }

                if (eventType.equals(EventType.EVENT_CLOSE) || eventType.equals(EventType.EVENT_OPEN)) {
                    // Used or WasGeneratedBy
                    properties.put(OPMConstants.OPM, edge.type());
                } else if (eventType.equals(EventType.EVENT_CHANGE_PRINCIPAL)) {
                    properties.put(OPMConstants.EDGE_OPERATION, edge.getAnnotation(OPMConstants.EDGE_OPERATION));
                }

                String eventOperation = edge.getAnnotation(OPMConstants.EDGE_OPERATION);
                if (OPMConstants.OPERATION_INIT_MODULE.equals(eventOperation) // init_module
                        || OPMConstants.OPERATION_FINIT_MODULE.equals(eventOperation) // finit_module
                        || OPMConstants.OPERATION_VMSPLICE.equals(eventOperation) // vmsplice only
                        || OPMConstants.OPERATION_TEE.equals(eventOperation) // tee
                        || OPMConstants.OPERATION_SPLICE.equals(eventOperation)) { // splice
                    properties.put(OPMConstants.EDGE_OPERATION, eventOperation);
                }

                Event event = new Event(uuid, sequence, eventType, threadId, subjectUUID, predicateObjectUUID,
                        predicateObjectPath, predicateObject2UUID, predicateObject2Path, timestampNanos, null, null,
                        location, size, null, properties);

                publishRecords(Arrays.asList(buildTcCDMDatum(event, source)));

            }
        }
    }

    private boolean publishSubjectAndPrincipal(AbstractVertex process) {

        if (isProcessVertex(process)) {

            String pid = process.getAnnotation(OPMConstants.PROCESS_PID);

            InstrumentationSource subjectSource = getInstrumentationSource(
                    process.getAnnotation(OPMConstants.SOURCE));

            if (subjectSource != null) {

                // Agents cannot come from BEEP source. Added just in case.
                InstrumentationSource principalSource = subjectSource
                        .equals(InstrumentationSource.SOURCE_LINUX_BEEP_TRACE)
                                ? InstrumentationSource.SOURCE_LINUX_SYSCALL_TRACE
                                : subjectSource;

                List<GenericContainer> objectsToPublish = new ArrayList<GenericContainer>();

                Principal principal = getPrincipalFromProcess(process);
                if (principal != null) {
                    UUID principalUUID = principal.getUuid();

                    if (!publishedPrincipals.contains(principalUUID)) {
                        objectsToPublish.add(buildTcCDMDatum(principal, principalSource));
                        publishedPrincipals.add(principalUUID);
                    }

                    Subject subject = getSubjectFromProcess(process, principalUUID);
                    if (subject != null) {
                        objectsToPublish.add(buildTcCDMDatum(subject, subjectSource));
                        if (subject.getType().equals(SubjectType.SUBJECT_PROCESS)) { // not in case of unit
                            // The map is used only for getting the parent subject UUID i.e. equivalent
                            // of ppid in OPM and that can only be the containing process as in Audit
                            pidSubjectUUID.put(pid, subject.getUuid());
                        }
                        return publishRecords(objectsToPublish) > 0;
                    } else {
                        return false;
                    }
                } else {
                    return false;
                }
            } else {
                logger.log(Level.WARNING, "Failed to publish '{0}' vertex because of missing/invalid source",
                        new Object[] { process });
            }
        }
        return false;
    }

    private Subject getSubjectFromProcess(AbstractVertex process, UUID principalUUID) {
        if (isProcessVertex(process) && principalUUID != null) {

            // Based on complete process annotations along with agent annotations
            UUID subjectUUID = getUuid(process);

            SubjectType subjectType = SubjectType.SUBJECT_PROCESS; // default
            if (process.getAnnotation(OPMConstants.PROCESS_ITERATION) != null) {
                subjectType = SubjectType.SUBJECT_UNIT; // if a unit
            }

            String pidString = process.getAnnotation(OPMConstants.PROCESS_PID);
            Integer pid = CommonFunctions.parseInt(pidString, null);
            if (pid == null) {
                logger.log(Level.WARNING, "Invalid pid {0} for Process {1}", new Object[] { pidString, process });
            } else {

                // Mandatory but if missing then default value is 0
                Long startTime = convertTimeToNanoseconds(null,
                        process.getAnnotation(OPMConstants.PROCESS_START_TIME), 0L);

                String unitIdAnnotation = process.getAnnotation(OPMConstants.PROCESS_UNIT);
                Integer unitId = CommonFunctions.parseInt(unitIdAnnotation, null);

                // meaning that the unit annotation was non-numeric.
                // Can't simply check for null because null is valid in case units=false in Audit
                if (unitId == null && unitIdAnnotation != null) {
                    logger.log(Level.WARNING, "Unexpected 'unit' value {0} for process {1}",
                            new Object[] { unitIdAnnotation, process });
                } else {

                    String iterationAnnotation = process.getAnnotation(OPMConstants.PROCESS_ITERATION);
                    Integer iteration = CommonFunctions.parseInt(iterationAnnotation, null);

                    if (iteration == null && iterationAnnotation != null) {
                        logger.log(Level.WARNING, "Unexpected 'iteration' value {0} for process {1}",
                                new Object[] { iterationAnnotation, process });
                    } else {

                        String countAnnotation = process.getAnnotation(OPMConstants.PROCESS_COUNT);
                        Integer count = CommonFunctions.parseInt(countAnnotation, null);

                        if (count == null && countAnnotation != null) {
                            logger.log(Level.WARNING, "Unexpected 'count' value {0} for process {1}",
                                    new Object[] { countAnnotation, process });
                        } else {

                            String cmdLine = process.getAnnotation(OPMConstants.PROCESS_COMMAND_LINE);
                            String ppid = process.getAnnotation(OPMConstants.PROCESS_PPID);

                            Map<CharSequence, CharSequence> properties = new HashMap<>();
                            addIfNotNull(OPMConstants.PROCESS_NAME, process.getAnnotations(), properties);
                            addIfNotNull(OPMConstants.PROCESS_CWD, process.getAnnotations(), properties);
                            addIfNotNull(OPMConstants.PROCESS_PPID, process.getAnnotations(), properties);
                            addIfNotNull(OPMConstants.PROCESS_SEEN_TIME, process.getAnnotations(), properties);

                            Subject subject = new Subject(subjectUUID, subjectType, pid, pidSubjectUUID.get(ppid),
                                    principalUUID, startTime, unitId, iteration, count, cmdLine, null, null, null,
                                    properties);

                            return subject;

                        }

                    }

                }

            }

        } else {
            logger.log(Level.WARNING, "Missing Process vertex {0} or Principal UUID {1}",
                    new Object[] { process, principalUUID });
        }
        return null;
    }

    // can return null
    private Principal getPrincipalFromProcess(AbstractVertex process) {
        if (isProcessVertex(process)) {
            AbstractVertex agentVertex = getAgentFromProcess(process);

            UUID uuid = getUuid(agentVertex);
            String userId = agentVertex.getAnnotation(OPMConstants.AGENT_UID);
            if (userId != null) {
                Map<CharSequence, CharSequence> properties = new HashMap<CharSequence, CharSequence>();
                addIfNotNull(OPMConstants.AGENT_EUID, agentVertex.getAnnotations(), properties);
                addIfNotNull(OPMConstants.AGENT_SUID, agentVertex.getAnnotations(), properties);
                addIfNotNull(OPMConstants.AGENT_FSUID, agentVertex.getAnnotations(), properties);

                List<CharSequence> groupIds = new ArrayList<CharSequence>();
                if (agentVertex.getAnnotation(OPMConstants.AGENT_GID) != null) {
                    groupIds.add(agentVertex.getAnnotation(OPMConstants.AGENT_GID));
                }
                if (agentVertex.getAnnotation(OPMConstants.AGENT_EGID) != null) {
                    groupIds.add(agentVertex.getAnnotation(OPMConstants.AGENT_EGID));
                }
                if (agentVertex.getAnnotation(OPMConstants.AGENT_SGID) != null) {
                    groupIds.add(agentVertex.getAnnotation(OPMConstants.AGENT_SGID));
                }
                if (agentVertex.getAnnotation(OPMConstants.AGENT_FSGID) != null) {
                    groupIds.add(agentVertex.getAnnotation(OPMConstants.AGENT_FSGID));
                }
                Principal principal = new Principal(uuid, PrincipalType.PRINCIPAL_LOCAL, userId, null, groupIds,
                        properties);
                return principal;
            } else {
                logger.log(Level.WARNING, "Missing 'uid' in agent vertex");
                return null;
            }
        } else {
            logger.log(Level.WARNING, "Vertex type MUST be Process but found {0}", new Object[] { process });
            return null;
        }
    }

    // Argument must be of type process and must be ensured by the caller
    private AbstractVertex getAgentFromProcess(AbstractVertex process) {
        AbstractVertex agentVertex = new Agent();
        agentVertex.addAnnotation(OPMConstants.SOURCE, OPMConstants.SOURCE_AUDIT_SYSCALL);
        for (String agentAnnotation : agentAnnotations) {
            String agentAnnotationValue = process.getAnnotation(agentAnnotation);
            if (agentAnnotationValue != null && !"".equals(agentAnnotationValue)) {
                agentVertex.addAnnotation(agentAnnotation, agentAnnotationValue);
            }
        }
        return agentVertex;
    }

    private boolean publishHost(AbstractVertex vertex) {
        if (vertex != null) {
            UUID uuid = getUuid(vertex);
            String hostName = vertex.getAnnotation(OPMConstants.ARTIFACT_HOST_NETWORK_NAME);
            String serialNumber = vertex.getAnnotation(OPMConstants.ARTIFACT_HOST_SERIAL_NUMBER);
            HostIdentifier hostIdentifier = new HostIdentifier(OPMConstants.ARTIFACT_HOST_SERIAL_NUMBER,
                    serialNumber);
            List<HostIdentifier> hostIdentifiers = Arrays.asList(hostIdentifier);
            String operatingSystem = vertex.getAnnotation(OPMConstants.ARTIFACT_HOST_OPERATING_SYSTEM);
            HostType hostType = HostType.HOST_DESKTOP; // TODO always DESKTOP in AUDIT for now.
            List<Interface> interfaces = new ArrayList<Interface>();
            String interfacesCountString = vertex.getAnnotation(OPMConstants.ARTIFACT_HOST_INTERFACES_COUNT);
            Integer interfacesCount = CommonFunctions.parseInt(interfacesCountString, null);
            if (interfacesCount != null) {
                for (int a = 0; a < interfacesCount; a++) {
                    String interfaceName = vertex.getAnnotation(OPMConstants.buildHostNetworkInterfaceNameKey(a));
                    String interfaceMacAddress = vertex
                            .getAnnotation(OPMConstants.buildHostNetworkInterfaceMacAddressKey(a));
                    String interfaceIpAddresses = vertex
                            .getAnnotation(OPMConstants.buildHostNetworkInterfaceIpAddressesKey(a));
                    interfaceIpAddresses = interfaceIpAddresses == null ? "" : interfaceIpAddresses;
                    List<CharSequence> ipAddressesList = OPMConstants
                            .parseHostNetworkInterfaceIpAddressesValue(interfaceIpAddresses);
                    Interface interfaze = new Interface(interfaceName, interfaceMacAddress, ipAddressesList);
                    interfaces.add(interfaze);
                }
            }
            Host host = new Host(uuid, hostName, hostIdentifiers, operatingSystem, hostType, interfaces);
            if (publishRecords(
                    Arrays.asList(buildTcCDMDatum(host, InstrumentationSource.SOURCE_LINUX_SYSCALL_TRACE))) > 0) {
                return true;
            } else {
                return false;
            }
        } else {
            return false;
        }
    }

    private boolean publishArtifact(AbstractVertex vertex) {
        InstrumentationSource source = getInstrumentationSource(vertex.getAnnotation(OPMConstants.SOURCE));
        if (source == null) {
            return false;
        } else {
            // Make sure all artifacts without epoch are being treated fine. 
            String epochAnnotation = vertex.getAnnotation(OPMConstants.ARTIFACT_EPOCH);
            Integer epoch = CommonFunctions.parseInt(epochAnnotation, null);
            // Non-numeric value for epoch
            if (epoch == null && epochAnnotation != null) {
                logger.log(Level.WARNING, "Epoch annotation {0} must be of type LONG",
                        new Object[] { epochAnnotation });
                return false;
            } else {

                Object tccdmObject = null;

                String artifactType = vertex.getAnnotation(OPMConstants.ARTIFACT_SUBTYPE);
                if (OPMConstants.SUBTYPE_NETWORK_SOCKET.equals(artifactType)) {

                    String srcAddress = vertex.getAnnotation(OPMConstants.ARTIFACT_LOCAL_ADDRESS);
                    String srcPort = vertex.getAnnotation(OPMConstants.ARTIFACT_LOCAL_PORT);
                    String destAddress = vertex.getAnnotation(OPMConstants.ARTIFACT_REMOTE_ADDRESS);
                    String destPort = vertex.getAnnotation(OPMConstants.ARTIFACT_REMOTE_PORT);
                    String protocolName = vertex.getAnnotation(OPMConstants.ARTIFACT_PROTOCOL); //can be null
                    Integer protocol = Audit.getProtocolNumber(protocolName);

                    srcAddress = srcAddress == null ? "" : srcAddress;
                    destAddress = destAddress == null ? "" : destAddress;
                    srcPort = srcPort == null ? "0" : srcPort;
                    destPort = srcPort == null ? "0" : destPort;

                    Map<CharSequence, CharSequence> properties = new HashMap<CharSequence, CharSequence>();
                    addIfNotNull(OPMConstants.ARTIFACT_VERSION, vertex.getAnnotations(), properties);

                    AbstractObject baseObject = new AbstractObject(null, epoch, properties);

                    tccdmObject = new NetFlowObject(getUuid(vertex), baseObject, srcAddress,
                            CommonFunctions.parseInt(srcPort, 0), destAddress,
                            CommonFunctions.parseInt(destPort, 0), protocol, null);

                } else if (OPMConstants.SUBTYPE_MEMORY_ADDRESS.equals(artifactType)) {

                    try {
                        Long memoryAddress = Long
                                .parseLong(vertex.getAnnotation(OPMConstants.ARTIFACT_MEMORY_ADDRESS), 16);
                        Long size = null;
                        if (vertex.getAnnotation(OPMConstants.ARTIFACT_SIZE) != null
                                && !vertex.getAnnotation(OPMConstants.ARTIFACT_SIZE).trim().isEmpty()) {
                            size = Long.parseLong(vertex.getAnnotation(OPMConstants.ARTIFACT_SIZE), 16);
                        }

                        Map<CharSequence, CharSequence> properties = new HashMap<CharSequence, CharSequence>();
                        addIfNotNull(OPMConstants.ARTIFACT_VERSION, vertex.getAnnotations(), properties);
                        addIfNotNull(OPMConstants.ARTIFACT_TGID, vertex.getAnnotations(), properties);

                        AbstractObject baseObject = new AbstractObject(null, epoch, properties);

                        tccdmObject = new MemoryObject(getUuid(vertex), baseObject, memoryAddress, null, null,
                                size);

                    } catch (NumberFormatException nfe) {
                        logger.log(Level.WARNING,
                                "Failed to parse memory address or size: " + ""
                                        + vertex.getAnnotation(OPMConstants.ARTIFACT_MEMORY_ADDRESS) + ", "
                                        + vertex.getAnnotation(OPMConstants.ARTIFACT_SIZE),
                                nfe);
                        return false;
                    } catch (Exception e) {
                        logger.log(Level.SEVERE, null, e);
                        return false;
                    }

                } else if (OPMConstants.SUBTYPE_UNNAMED_PIPE.equals(artifactType)) {

                    Integer sourceFileDescriptor = CommonFunctions
                            .parseInt(vertex.getAnnotation(OPMConstants.ARTIFACT_READ_FD), null);
                    Integer sinkFileDescriptor = CommonFunctions
                            .parseInt(vertex.getAnnotation(OPMConstants.ARTIFACT_WRITE_FD), null);

                    if (sourceFileDescriptor == null || sinkFileDescriptor == null) {
                        logger.log(Level.WARNING, "Error parsing src/sink fds in artifact {0}",
                                new Object[] { vertex });
                        return false;
                    } else {
                        Map<CharSequence, CharSequence> properties = new HashMap<>();
                        addIfNotNull(OPMConstants.ARTIFACT_VERSION, vertex.getAnnotations(), properties);
                        addIfNotNull(OPMConstants.ARTIFACT_TGID, vertex.getAnnotations(), OPMConstants.ARTIFACT_PID,
                                properties);

                        AbstractObject baseObject = new AbstractObject(null, epoch, properties);

                        tccdmObject = new IpcObject(getUuid(vertex), baseObject,
                                IpcObjectType.IPC_OBJECT_PIPE_UNNAMED, null, null, sourceFileDescriptor,
                                sinkFileDescriptor);
                    }

                } else if (OPMConstants.SUBTYPE_UNNAMED_NETWORK_SOCKET_PAIR.equals(artifactType)
                        || OPMConstants.SUBTYPE_UNNAMED_UNIX_SOCKET_PAIR.equals(artifactType)) {
                    String tgid = vertex.getAnnotation(OPMConstants.ARTIFACT_TGID);
                    Integer fd0 = CommonFunctions.parseInt(vertex.getAnnotation(OPMConstants.ARTIFACT_FD0), null);
                    Integer fd1 = CommonFunctions.parseInt(vertex.getAnnotation(OPMConstants.ARTIFACT_FD1), null);
                    if (tgid == null || fd0 == null || fd1 == null) {
                        logger.log(Level.WARNING, "Error parsing tgid/fd0/fd1 in artifact {0}",
                                new Object[] { vertex });
                        return false;
                    } else {
                        Map<CharSequence, CharSequence> properties = new HashMap<CharSequence, CharSequence>();
                        if (OPMConstants.SUBTYPE_UNNAMED_NETWORK_SOCKET_PAIR.equals(artifactType)) {
                            Integer protocol = Audit
                                    .getProtocolNumber(vertex.getAnnotation(OPMConstants.ARTIFACT_PROTOCOL));
                            properties.put(OPMConstants.ARTIFACT_PROTOCOL, String.valueOf(protocol));
                        }
                        properties.put(OPMConstants.ARTIFACT_PID, String.valueOf(tgid));
                        properties.put(OPMConstants.ARTIFACT_SUBTYPE, artifactType);
                        addIfNotNull(OPMConstants.ARTIFACT_VERSION, vertex.getAnnotations(), properties);

                        AbstractObject baseObject = new AbstractObject(null, epoch, properties);

                        tccdmObject = new IpcObject(getUuid(vertex), baseObject,
                                IpcObjectType.IPC_OBJECT_SOCKET_PAIR, null, null, fd0, fd1);
                    }
                } else if (OPMConstants.SUBTYPE_UNKNOWN.equals(artifactType)) {

                    Integer tgid = CommonFunctions.parseInt(vertex.getAnnotation(OPMConstants.ARTIFACT_TGID), null);
                    Integer fd = CommonFunctions.parseInt(vertex.getAnnotation(OPMConstants.ARTIFACT_FD), null);
                    if (fd == null || tgid == null) {
                        logger.log(Level.WARNING, "Error parsing tgid/fd in artifact {0}", new Object[] { vertex });
                        return false;
                    } else {

                        Map<CharSequence, CharSequence> properties = new HashMap<CharSequence, CharSequence>();
                        properties.put(OPMConstants.ARTIFACT_PID, String.valueOf(tgid));
                        addIfNotNull(OPMConstants.ARTIFACT_VERSION, vertex.getAnnotations(), properties);

                        AbstractObject baseObject = new AbstractObject(null, epoch, properties);

                        tccdmObject = new SrcSinkObject(getUuid(vertex), baseObject, SrcSinkType.SRCSINK_UNKNOWN,
                                fd);
                    }

                } else if (OPMConstants.SUBTYPE_NAMED_PIPE.equals(artifactType)) {

                    String permissionsAnnotation = vertex.getAnnotation(OPMConstants.ARTIFACT_PERMISSIONS);
                    SHORT permissions = getPermissionsAsCDMSHORT(permissionsAnnotation);

                    Map<CharSequence, CharSequence> properties = new HashMap<>();
                    addIfNotNull(OPMConstants.ARTIFACT_VERSION, vertex.getAnnotations(), properties);
                    addIfNotNull(OPMConstants.ARTIFACT_PATH, vertex.getAnnotations(), properties);

                    AbstractObject baseObject = new AbstractObject(permissions, epoch, properties);

                    tccdmObject = new IpcObject(getUuid(vertex), baseObject, IpcObjectType.IPC_OBJECT_PIPE_NAMED,
                            null, null, null, null);

                } else if (artifactType != null) {
                    FileObjectType fileObjectType = null;
                    switch (artifactType) {
                    case OPMConstants.SUBTYPE_FILE:
                        fileObjectType = FileObjectType.FILE_OBJECT_FILE;
                        break;
                    case OPMConstants.SUBTYPE_DIRECTORY:
                        fileObjectType = FileObjectType.FILE_OBJECT_DIR;
                        break;
                    case OPMConstants.SUBTYPE_BLOCK_DEVICE:
                        fileObjectType = FileObjectType.FILE_OBJECT_BLOCK;
                        break;
                    case OPMConstants.SUBTYPE_CHARACTER_DEVICE:
                        fileObjectType = FileObjectType.FILE_OBJECT_CHAR;
                        break;
                    case OPMConstants.SUBTYPE_LINK:
                        fileObjectType = FileObjectType.FILE_OBJECT_LINK;
                        break;
                    case OPMConstants.SUBTYPE_UNIX_SOCKET:
                        fileObjectType = FileObjectType.FILE_OBJECT_UNIX_SOCKET;
                        break;
                    default:
                        break;
                    }
                    if (fileObjectType != null) {
                        tccdmObject = createFileObject(getUuid(vertex),
                                vertex.getAnnotation(OPMConstants.ARTIFACT_PATH),
                                vertex.getAnnotation(OPMConstants.ARTIFACT_VERSION), epoch,
                                vertex.getAnnotation(OPMConstants.ARTIFACT_PERMISSIONS), fileObjectType);
                    } else {
                        logger.log(Level.WARNING, "Unexpected artifact subtype {0}", new Object[] { vertex });
                        return false;
                    }
                } else {
                    logger.log(Level.WARNING, "NULL artifact subtype {0}", new Object[] { vertex });
                    return false;
                }
                if (tccdmObject != null) {
                    return publishRecords(Arrays.asList(buildTcCDMDatum(tccdmObject, source))) > 0;
                } else {
                    logger.log(Level.WARNING, "Failed to create Object from Artifact");
                    return false;
                }
            }
        }
    }

    /**
     * Creates a CDM FileObject from the given arguments
     * 
     * @param uuid UUID of the whole artifact vertex
     * @param path path of the file
     * @param version version annotation
     * @param epoch epoch annotation
     * @param permissionsAnnotation permissions in octal string format (can be null)
     * @param type FileObjectType
     * @return FileObject instance
     */
    private FileObject createFileObject(UUID uuid, String path, String version, Integer epoch,
            String permissionsAnnotation, FileObjectType type) {

        Map<CharSequence, CharSequence> properties = new HashMap<CharSequence, CharSequence>();
        if (path != null) {
            properties.put(OPMConstants.ARTIFACT_PATH, path);
        }
        if (version != null) {
            properties.put(OPMConstants.ARTIFACT_VERSION, version);
        }

        SHORT permissions = getPermissionsAsCDMSHORT(permissionsAnnotation);

        AbstractObject baseObject = new AbstractObject(permissions, epoch, properties);
        FileObject fileObject = new FileObject(uuid, baseObject, type, null, null, null, null, null);

        return fileObject;
    }

    private void publishCurrentTimeMarker() {
        long currentTimeNanos = System.currentTimeMillis() * 1000 * 1000; // millis to nanos
        TimeMarker timeMarker = new TimeMarker(currentTimeNanos);
        publishRecords(
                Arrays.asList(buildTcCDMDatum(timeMarker, InstrumentationSource.SOURCE_LINUX_SYSCALL_TRACE)));
    }

    /**
     * 
     * Annotations: 'end time' for end of stream
     */
    private void publishEndStreamAndTimeMarkerObjects() {
        // First publish time marker and then publish session start/end marker
        publishCurrentTimeMarker();
        Object sessionMarker = null;
        // manually add the session end marker count
        incrementStatsCount(EndMarker.class.getSimpleName());
        Map<CharSequence, CharSequence> recordCounts = new HashMap<>();
        for (Map.Entry<String, Long> entry : this.stats.entrySet()) {
            recordCounts.put(entry.getKey(), String.valueOf(entry.getValue()));
        }
        sessionMarker = new EndMarker(this.sessionNumber, recordCounts);
        publishRecords(
                Arrays.asList(buildTcCDMDatum(sessionMarker, InstrumentationSource.SOURCE_LINUX_SYSCALL_TRACE)));
    }

    @Override
    /**
     * Calls the superclass's publishRecords method after updating the object
     * type count.
     */
    protected int publishRecords(List<GenericContainer> genericContainers) {
        if (genericContainers != null) {
            for (GenericContainer genericContainer : genericContainers) {
                try {
                    if (genericContainer instanceof TCCDMDatum) {
                        Object cdmObject = ((TCCDMDatum) genericContainer).getDatum();
                        if (cdmObject != null) {
                            if (cdmObject.getClass().equals(Subject.class)) {
                                SubjectType subjectType = ((Subject) cdmObject).getType();
                                if (subjectType != null) {
                                    incrementStatsCount(subjectType.name());
                                }
                            } else if (cdmObject.getClass().equals(Event.class)) {
                                EventType eventType = ((Event) cdmObject).getType();
                                if (eventType != null) {
                                    String keyName = eventType.name();
                                    if (eventType.equals(EventType.EVENT_OTHER)) {
                                        CharSequence keyNameCharSeq = ((Event) cdmObject).getProperties()
                                                .get(OPMConstants.EDGE_OPERATION);
                                        if (keyNameCharSeq != null) {
                                            keyName = String.valueOf(keyNameCharSeq);
                                        }
                                    }
                                    incrementStatsCount(keyName);
                                }
                            } else if (cdmObject.getClass().equals(SrcSinkObject.class)) {
                                SrcSinkObject sso = ((SrcSinkObject) cdmObject);
                                if (sso.getType().equals(SrcSinkType.SRCSINK_UNKNOWN)) {
                                    String subtype = SrcSinkObject.class.getSimpleName();
                                    if (sso.getBaseObject() != null) {
                                        if (sso.getBaseObject().getProperties() != null) {
                                            CharSequence subtypeCharSeq = sso.getBaseObject().getProperties()
                                                    .get(OPMConstants.ARTIFACT_SUBTYPE);
                                            if (subtypeCharSeq != null) {
                                                subtype = subtypeCharSeq.toString();
                                            }
                                        }
                                    }
                                    incrementStatsCount(subtype);
                                }
                            } else if (cdmObject.getClass().equals(FileObject.class)) {
                                FileObjectType fileObjectType = ((FileObject) cdmObject).getType();
                                if (fileObjectType != null) {
                                    incrementStatsCount(fileObjectType.name());
                                }
                            } else if (cdmObject.getClass().equals(IpcObject.class)) {
                                IpcObjectType ipcObjectType = ((IpcObject) cdmObject).getType();
                                if (ipcObjectType != null) {
                                    incrementStatsCount(ipcObjectType.name());
                                }
                            } else {
                                incrementStatsCount(cdmObject.getClass().getSimpleName());
                            }
                        }
                    }
                } catch (Exception e) {
                    logger.log(Level.WARNING, "Failed to collect stats", e);
                }
            }
            return super.publishRecords(genericContainers);
        } else {
            return 0;
        }
    }

    @Override
    protected Properties getDefaultKafkaProducerProperties(String kafkaServer, String kafkaTopic,
            String kafkaProducerID, String schemaFilename) {
        Properties properties = new Properties();
        properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, kafkaServer);
        properties.put(ProducerConfig.CLIENT_ID_CONFIG, kafkaProducerID);
        properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG,
                "org.apache.kafka.common.serialization.StringSerializer");
        properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,
                com.bbn.tc.schema.serialization.kafka.KafkaAvroGenericSerializer.class);
        properties.put(AvroConfig.SCHEMA_WRITER_FILE, schemaFilename);
        properties.put(AvroConfig.SCHEMA_SERDE_IS_SPECIFIC, true);
        if (useSsl) {
            properties.put(CommonClientConfigs.SECURITY_PROTOCOL_CONFIG, securityProtocol);
            properties.put(SslConfigs.SSL_TRUSTSTORE_LOCATION_CONFIG, trustStoreLocation);
            properties.put(SslConfigs.SSL_TRUSTSTORE_PASSWORD_CONFIG, trustStorePassword);
            properties.put(SslConfigs.SSL_KEYSTORE_LOCATION_CONFIG, keyStoreLocation);
            properties.put(SslConfigs.SSL_KEYSTORE_PASSWORD_CONFIG, keyStorePassword);
            properties.put(SslConfigs.SSL_KEY_PASSWORD_CONFIG, keyPassword);
        }
        return properties;
    }

    @Override
    public boolean initialize(String arguments) {
        Map<String, String> argumentsMap = CommonFunctions.parseKeyValPairs(arguments);
        String hexUUIDsArgValue = argumentsMap.get("hexUUIDs");
        if (hexUUIDsArgValue != null) {
            hexUUIDsArgValue = hexUUIDsArgValue.trim();
            if ("false".equals(hexUUIDsArgValue)) {
                hexUUIDs = false;
            } else if ("true".equals(hexUUIDsArgValue)) {
                hexUUIDs = true;
            } else {
                logger.log(Level.SEVERE,
                        "Invalid 'hexUUIDs' value: " + hexUUIDsArgValue + ". Only 'true' or 'false'");
                return false;
            }
        }

        String cdmOutFilePath = Settings.getDefaultOutputFilePath(this.getClass());
        Map<String, String> cdmOutFileKeyValues = null;
        try {
            if (FileUtility.doesPathExist(cdmOutFilePath)) {
                if (!FileUtility.isFile(cdmOutFilePath)) {
                    logger.log(Level.SEVERE, "CDM storage output file not a file: " + cdmOutFilePath);
                    return false;
                } else {
                    try {
                        cdmOutFileKeyValues = FileUtility.readConfigFileAsKeyValueMap(cdmOutFilePath, "=");
                    } catch (Exception e) {
                        logger.log(Level.SEVERE, "Failed to read CDM storage output file: " + cdmOutFilePath, e);
                        return false;
                    }
                }
            }
        } catch (Exception e) {
            logger.log(Level.SEVERE, "Failed to check if CDM storage output file is a file: " + cdmOutFilePath, e);
            return false;
        }

        String lastSessionKey = "lastSession";
        String sessionKey = "session";
        String sessionNumberString = argumentsMap.get(sessionKey);
        if (sessionNumberString != null) {
            Integer sessionNumber = CommonFunctions.parseInt(sessionNumberString, null);
            if (sessionNumber == null) {
                logger.log(Level.SEVERE, "'" + sessionKey + "' must be an 'int': " + sessionNumberString);
                return false;
            } else {
                this.sessionNumber = sessionNumber;
            }
        } else { // no session number in args. read from file
            if (cdmOutFileKeyValues != null) {
                String lastSessionString = cdmOutFileKeyValues.get(lastSessionKey);
                if (lastSessionString != null) {
                    Integer lastSessionInteger = CommonFunctions.parseInt(lastSessionString, null);
                    if (lastSessionInteger == null) {
                        logger.log(Level.SEVERE, "Invalid '" + lastSessionKey + "' value '" + lastSessionString
                                + "' in file: " + cdmOutFilePath);
                        return false;
                    } else {
                        this.sessionNumber = lastSessionInteger + 1;
                    }
                }
            }
        }

        String createHostConfigArgValue = argumentsMap.get("createHostConfig");
        if (createHostConfigArgValue != null) {
            createHostConfigArgValue = createHostConfigArgValue.trim();
            if ("false".equals(createHostConfigArgValue)) {
                createHostConfig = false;
            } else if ("true".equals(createHostConfigArgValue)) {
                createHostConfig = true;
            } else {
                logger.log(Level.SEVERE, "Invalid 'createHostConfig' value: " + createHostConfigArgValue
                        + ". Only 'true' or 'false'");
                return false;
            }
        }

        // Populate the ssl configs before calling parent's initialize because of ssl properties.
        String sslArgValue = argumentsMap.get("ssl");
        if (sslArgValue != null) {
            if ("false".equals(sslArgValue)) {
                useSsl = false;
            } else if ("true".equals(sslArgValue)) {
                useSsl = true;
            } else {
                logger.log(Level.SEVERE, "Invalid 'ssl' value: " + useSsl + ". Only 'true' or 'false'");
                return false;
            }
        }

        // Only do the following checks in case of server
        if (useSsl && writeDataToServer(argumentsMap)) {
            String configFile = Settings.getDefaultConfigFilePath(this.getClass());
            try {
                Map<String, String> configMap = FileUtility.readConfigFileAsKeyValueMap(configFile, "=");
                securityProtocol = configMap.get("SecurityProtocol");
                trustStoreLocation = configMap.get("TrustStoreLocation");
                trustStorePassword = configMap.get("TrustStorePassword");
                keyStoreLocation = configMap.get("KeyStoreLocation");
                keyStorePassword = configMap.get("KeyStorePassword");
                keyPassword = configMap.get("KeyPassword");
                if (securityProtocol == null || trustStoreLocation == null || trustStorePassword == null
                        || keyStoreLocation == null || keyStorePassword == null || keyPassword == null) {
                    logger.log(Level.SEVERE,
                            "In config file the following keys must be defined: 'SecurityProtocol', 'TrustStoreLocation', "
                                    + "'TrustStorePassword', 'KeyStoreLocation', 'KeyStorePassword', 'KeyPassword'");
                    return false;
                }
                try {
                    if (!FileUtility.doesPathExist(trustStoreLocation)) {
                        logger.log(Level.SEVERE,
                                "Path specified for 'TrustStoreLocation' key in config does not exist: "
                                        + trustStoreLocation);
                        return false;
                    }
                } catch (Exception e) {
                    logger.log(Level.SEVERE,
                            "Failed to check if path specified for 'TrustStoreLocation' key in config exists: "
                                    + trustStoreLocation,
                            e);
                    return false;
                }
                try {
                    if (!FileUtility.doesPathExist(keyStoreLocation)) {
                        logger.log(Level.SEVERE,
                                "Path specified for 'KeyStoreLocation' key in config does not exist: "
                                        + keyStoreLocation);
                        return false;
                    }
                } catch (Exception e) {
                    logger.log(Level.SEVERE,
                            "Failed to check if path specified for 'KeyStoreLocation' key in config exists: "
                                    + keyStoreLocation,
                            e);
                    return false;
                }
            } catch (Exception e) {
                logger.log(Level.SEVERE, "Failed to read config file: " + configFile);
                return false;
            }
        }

        boolean initResult = super.initialize(arguments);
        if (!initResult) {
            return false;
        } else {
            AbstractVertex hostVertex = createHostVertex();
            if (hostVertex == null) {
                return false;
            } else {
                cdmOutFileKeyValues = getMapWithSessionNumber(cdmOutFileKeyValues, lastSessionKey,
                        this.sessionNumber);
                if (!writeOutFile(cdmOutFilePath, cdmOutFileKeyValues)) {
                    return false;
                }
                logger.log(Level.INFO, "Session number = " + this.sessionNumber);
                populateEventRules();
                // Set the Host UUID before publishing any record
                this.hostUUID = getHostUUID(hostVertex);
                // Host is always the first record of the steam now.
                publishHost(hostVertex);
                // Publish the time marker after the host record
                publishCurrentTimeMarker();

            }
            return true;
        }
    }

    private Map<String, String> getMapWithSessionNumber(Map<String, String> keyValues, String sessionKey,
            int session) {
        if (keyValues == null) {
            keyValues = new HashMap<String, String>();
        }
        keyValues.put(sessionKey, String.valueOf(session));
        return keyValues;
    }

    private boolean writeOutFile(String outFilePath, Map<String, String> keyValues) {
        try {
            List<String> lines = CommonFunctions.mapToLines(keyValues, "=");
            if (lines == null) {
                logger.log(Level.SEVERE, "NULL map for writing out to CDM storage output file");
                return false;
            } else {
                FileUtility.writeLines(outFilePath, lines);
                return true;
            }
        } catch (Exception e) {
            logger.log(Level.SEVERE,
                    "Failed to write map (" + keyValues + ") to CDM storage output file: " + outFilePath, e);
            return false;
        }
    }

    private AbstractVertex createHostVertex() {
        String configFilePath = Settings.getDefaultConfigFilePath(this.getClass());
        Map<String, String> configMap = null;
        try {
            configMap = FileUtility.readConfigFileAsKeyValueMap(configFilePath, "=");
        } catch (Exception e) {
            logger.log(Level.SEVERE, "Failed to read config file: " + configFilePath, e);
            return null;
        }

        String hostFileKey = "hostFile";
        String hostFilePath = configMap.get(hostFileKey);

        if (hostFilePath == null || (hostFilePath = hostFilePath.trim()).isEmpty()) {
            logger.log(Level.SEVERE, "Missing/Empty '" + hostFileKey + "' value in config");
            return null;
        } else {
            if (createHostConfig) {
                if (!HostInfo.generateCurrentHostFile(hostFilePath)) {
                    // Failed to write the host file.
                    return null;
                }
            }
            try {
                if (FileUtility.isFileReadable(hostFilePath)) {
                    HostInfo.Host hostInfo = HostInfo.ReadFromFile.readSafe(hostFilePath);
                    AbstractVertex hostVertex = new Artifact();
                    hostVertex.addAnnotations(hostInfo.getAnnotationsMap());
                    return hostVertex;
                } else {
                    logger.log(Level.SEVERE, "Host info file path is not readable: " + hostFilePath);
                    return null;
                }
            } catch (Exception e) {
                logger.log(Level.SEVERE, "Failed to check if host info file is readable: " + hostFilePath, e);
                return null;
            }
        }
    }

    /**
     * Relies on the deduplication functionality in the Audit reporter.
     * Doesn't do any deduplication itself.
     * 
     * @param incomingVertex AbstractVertex
     * @return true if successfully published. false if failed to publish or didn't publish
     */
    @Override
    public boolean putVertex(AbstractVertex incomingVertex) {
        currentVerticesAndEdges.add(incomingVertex);
        return true;
    }

    private boolean publishVertex(AbstractVertex incomingVertex) {
        try {
            if (incomingVertex != null) {
                String type = incomingVertex.type();

                if (isProcessVertex(incomingVertex)) {
                    return publishSubjectAndPrincipal(incomingVertex);
                } else if (OPMConstants.ARTIFACT.equals(type)) {
                    if (OPMConstants.SOURCE_AUDIT_NETFILTER
                            .equals(incomingVertex.getAnnotation(OPMConstants.SOURCE))) {
                        // Ignore until CDM updated with refine edge. TODO
                    } else {
                        return publishArtifact(incomingVertex);
                    }
                } else {
                    logger.log(Level.WARNING, "Unexpected vertex type {0}", new Object[] { type });
                }

            }
        } catch (Exception e) {
            logger.log(Level.WARNING, null, e);
        }
        return false;
    }

    /**
     * Gets the edge passed and based on it's timestamp and event id decides
     * whether to put it in the list of currentEventEdges or first process the 
     * edges already in the list and then put it in the currentEventEdges list.
     * 
     * The currentEventEdges list is processed and emptied whenever the timestamp
     * and event id changes to a new one from the last seen one
     * 
     * @param edge AbstractEdge
     * @return false if edge is null or is missing the time and event id annotations
     */
    @Override
    public boolean putEdge(AbstractEdge edge) {

        // ASSUMPTION that all edges for the same event are contiguously sent (Audit follows this)

        try {
            if (edge != null) {
                SimpleEntry<String, String> newEdgeTimeEventId = getTimeEventId(edge);

                if (newEdgeTimeEventId != null) {

                    if (lastTimeEventId == null) {
                        lastTimeEventId = newEdgeTimeEventId;
                    }

                    // handles the first edge case also
                    if (lastTimeEventId.equals(newEdgeTimeEventId)) {
                        currentVerticesAndEdges.add(edge);
                    } else {
                        // new time,eventid so flush the current edges and move to the next
                        publishVerticesAndEdges(currentVerticesAndEdges);
                        lastTimeEventId = newEdgeTimeEventId;
                        currentVerticesAndEdges.clear();
                        currentVerticesAndEdges.add(edge);
                    }

                    return true;
                } else {
                    return false;
                }

            } else {
                return false;
            }
        } catch (Exception e) {
            logger.log(Level.WARNING, null, e);
            return false;
        }
    }

    private void publishVerticesAndEdges(List<Object> objects) {
        /*
         * First process the vertices before the edges for this event
         * Then process the edges
         * Then process the vertices after the edges
         * 
         * Doing this to make sure that state set by a process vertex after an edge
         * isn't overwritten by an edge. Specifically the case of setuid/setgid over-
         * writing the subject uuid for a pid set by a process vertex later on.
         * 
         */
        // Create a copy to be safe
        List<Object> objectsCopy = new ArrayList<Object>(objects);

        /*
         * Handling the case where each event can now contain process agent update events
         * with the same time and event id. 
         * Iterating through the list and processing them first one by one (i.e. as each 
         * edge for process update is encountered). 
         * Then the rest of the list is processed (excluding the already processed one)
         * 
         * Note: Relies on NO mixing of vertices. Meaning that all vertices for an edge are seen
         * before than edge and there is no edge between a vertex and the corresponding edge.
         * 
         * Example: P1, P2, P2->P1, P4, P4->P2 (is correct), and P1, P2, P4, P2->P1, P4->P2 (is wrong)
         */
        int lastProcessUpdateEdgeIndex = 0;
        List<AbstractVertex> currentProcessUpdateVertices = new ArrayList<AbstractVertex>();
        for (int a = 0; a < objectsCopy.size(); a++) {
            Object object = objectsCopy.get(a);
            if (object instanceof AbstractVertex) {
                currentProcessUpdateVertices.add((AbstractVertex) object);
            } else if (object instanceof AbstractEdge) {
                AbstractEdge edge = (AbstractEdge) object;
                if (edge.type().equals(OPMConstants.WAS_TRIGGERED_BY)
                        && OPMConstants.OPERATION_UPDATE.equals(edge.getAnnotation(OPMConstants.EDGE_OPERATION))) {
                    for (AbstractVertex vertex : currentProcessUpdateVertices) {
                        publishVertex(vertex);
                    }
                    processEdgesWrapper(Arrays.asList(edge));
                    currentProcessUpdateVertices.clear();
                    lastProcessUpdateEdgeIndex = a + 1;
                }
            } else {
                logger.log(Level.WARNING, "Unexpected object type in: " + objects);
            }
        }

        // process the rest if there are any remaining vertices and edges
        if (objectsCopy.size() > lastProcessUpdateEdgeIndex) {
            objectsCopy = objectsCopy.subList(lastProcessUpdateEdgeIndex, objectsCopy.size());

            List<AbstractEdge> edges = new ArrayList<AbstractEdge>();
            List<AbstractVertex> verticesBeforeAndBetweenEdges = new ArrayList<AbstractVertex>();
            List<AbstractVertex> verticesAfterTheLastEdge = new ArrayList<AbstractVertex>();

            List<AbstractVertex> vertexListRef = verticesAfterTheLastEdge;

            for (int a = objectsCopy.size() - 1; a >= 0; a--) {
                Object object = objectsCopy.get(a);
                if (object instanceof AbstractVertex) {
                    vertexListRef.add((AbstractVertex) object);
                } else if (object instanceof AbstractEdge) {
                    vertexListRef = verticesBeforeAndBetweenEdges;
                    edges.add((AbstractEdge) object);
                } else {
                    logger.log(Level.WARNING, "Unexpected object type in: " + objects);
                }
            }

            for (AbstractVertex vertex : verticesBeforeAndBetweenEdges) {
                publishVertex(vertex);
            }
            processEdgesWrapper(edges);
            for (AbstractVertex vertex : verticesAfterTheLastEdge) {
                publishVertex(vertex);
            }
        }
    }

    // Handles special cases before calling processEdges
    private void processEdgesWrapper(List<AbstractEdge> edges) {
        // special cases
        boolean processIndividually = false;
        // If execve or setuid then multiple edges in the same time,eventid 
        // but need to process them separately
        if ((edgesContainTypeOperation(edges, new TypeOperation(OPMConstants.USED, OPMConstants.OPERATION_LOAD))
                || edgesContainTypeOperation(edges,
                        new TypeOperation(OPMConstants.WAS_TRIGGERED_BY, OPMConstants.OPERATION_EXECVE)))
                || (edgesContainTypeOperation(edges,
                        new TypeOperation(OPMConstants.WAS_TRIGGERED_BY, OPMConstants.OPERATION_SETUID)))
                || (edgesContainTypeOperation(edges,
                        new TypeOperation(OPMConstants.WAS_TRIGGERED_BY, OPMConstants.OPERATION_SETGID)))
                || (edgesContainTypeOperation(edges,
                        new TypeOperation(OPMConstants.WAS_TRIGGERED_BY, OPMConstants.OPERATION_UPDATE)))) {
            processIndividually = true;
        } else if (edgesContainTypeOperation(edges,
                new TypeOperation(OPMConstants.WAS_DERIVED_FROM, OPMConstants.OPERATION_UPDATE))) {

            // Process the update edge here first
            // Remove the update edge
            // Then send on the remaining edges to processEdges function
            AbstractEdge updateEdge = null;
            AbstractVertex actingVertex = null;
            for (AbstractEdge edge : edges) {
                if (OPMConstants.OPERATION_UPDATE.equals(edge.getAnnotation(OPMConstants.EDGE_OPERATION))) {
                    if (OPMConstants.SOURCE_AUDIT_NETFILTER.equals(edge.getAnnotation(OPMConstants.SOURCE))) {
                        // Means that this is the new edge: netfilter network -> WDF -> syscall network
                        // TODO update this when this event option added in CDM
                        // Ignoring the edge for now
                        // This edge comes from a netfilter event and has a unique time:eventid combo
                        // Only this (one) edge for this event
                        return;
                    }
                    updateEdge = edge;
                }
                if (OPMConstants.USED.equals(edge.getAnnotation(OPMConstants.TYPE))) {
                    actingVertex = edge.getChildVertex();
                } else if (OPMConstants.WAS_GENERATED_BY.equals(edge.getAnnotation(OPMConstants.TYPE))) {
                    actingVertex = edge.getParentVertex();
                }
            }

            if (updateEdge == null || actingVertex == null) {
                logger.log(Level.WARNING, "Missing acting vertex or update edge in edges: " + edges);
                return;
            } else {
                publishEvent(EventType.EVENT_UPDATE, updateEdge, actingVertex, updateEdge.getParentVertex(),
                        updateEdge.getChildVertex());

                // Remove the update edge and process the rest of the edges
                List<AbstractEdge> edgesCopy = new ArrayList<AbstractEdge>(edges);
                edgesCopy.remove(updateEdge);

                processEdges(edgesCopy);
                return;
            }

        } else if (edgesContainTypeOperation(edges,
                new TypeOperation(OPMConstants.WAS_TRIGGERED_BY, OPMConstants.OPERATION_UNIT_DEPENDENCY))) {
            // very special case. ah!
            // We can get operation=unit edges along with operation=unit dependency edges
            // Processing unit dependency edges individually here because they have no event type
            // Then sending all operation=unit edges (if any) ahead to be processed individually
            List<AbstractEdge> edgesCopy = new ArrayList<AbstractEdge>(edges);
            for (int a = edgesCopy.size() - 1; a > -1; a--) {
                AbstractEdge edge = edgesCopy.get(a);
                if (edge.getAnnotation(OPMConstants.EDGE_OPERATION).equals(OPMConstants.OPERATION_UNIT_DEPENDENCY)
                        && edge.getAnnotation(OPMConstants.TYPE).equals(OPMConstants.WAS_TRIGGERED_BY)) {
                    AbstractVertex acting = edge.getParentVertex();
                    AbstractVertex dependent = edge.getChildVertex();
                    UnitDependency unitDependency = new UnitDependency(getUuid(acting), getUuid(dependent));
                    publishRecords(Arrays.asList(
                            buildTcCDMDatum(unitDependency, InstrumentationSource.SOURCE_LINUX_BEEP_TRACE)));
                    edgesCopy.remove(a);
                }
            }
            if (edgesCopy.isEmpty()) {
                return;
            } else {
                processIndividually = true;
                edges = edgesCopy;
            }
        }

        if (processIndividually) {
            for (AbstractEdge currentEventEdge : edges) {
                processEdges(Arrays.asList(currentEventEdge));
            }
        } else {
            processEdges(edges);
        }
    }

    public boolean shutdown() {
        try {

            // Flush buffer
            publishVerticesAndEdges(currentVerticesAndEdges);
            currentVerticesAndEdges.clear();
            pidSubjectUUID.clear();
            publishedPrincipals.clear();

            publishEndStreamAndTimeMarkerObjects();

            return super.shutdown();
        } catch (Exception exception) {
            logger.log(Level.SEVERE, null, exception);
            return false;
        }
    }

    /**
     * Returns true if an edge has the same type and operation as the one given
     * 
     * @param edges list of edges to compare against
     * @param typeOperation TypeOperation object
     * @return true if matched else false
     */
    private boolean edgesContainTypeOperation(List<AbstractEdge> edges, TypeOperation typeOperation) {
        if (edges != null) {
            for (AbstractEdge edge : edges) {
                if (typeOperation.getType().equals(edge.getAnnotation(OPMConstants.TYPE))
                        && typeOperation.getOperation().equals(edge.getAnnotation(OPMConstants.EDGE_OPERATION))) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * Figures out the arguments needed for constructing an event
     * Modifies the global state to correctly keep track of pid to subject UUID
     * And after the event does post publishing steps
     * @param edges list of edges to process
     */
    private void processEdges(List<AbstractEdge> edges) {
        if (edges != null && edges.size() > 0) {

            AbstractVertex actedUpon1 = null, actedUpon2 = null;
            AbstractVertex actingVertex = null;
            AbstractEdge edgeForEvent = null;
            EventType eventType = getEventType(edges);

            if (edges.size() == 1) {
                edgeForEvent = edges.get(0);
                if (edgeForEvent != null) {
                    String edgeType = edgeForEvent.type();
                    String edgeOperation = edgeForEvent.getAnnotation(OPMConstants.EDGE_OPERATION);

                    if (OPMConstants.WAS_TRIGGERED_BY.equals(edgeType)) {

                        actingVertex = edgeForEvent.getParentVertex();
                        actedUpon1 = edgeForEvent.getChildVertex();

                        // Handling the case where a process A setuids and becomes A'
                        // and then A' setuid's to become A. If this is not done then 
                        // if process A creates a process C, then in putVertex the process
                        // C would get the pid for A' instead of A as it's parentProcessUUID
                        // Not doing this for UNIT vertices
                        if ((OPMConstants.OPERATION_SETUID.equals(edgeOperation)
                                || OPMConstants.OPERATION_SETGID.equals(edgeOperation)
                                || OPMConstants.OPERATION_UPDATE.equals(edgeOperation))
                                && actedUpon1.getAnnotation(OPMConstants.PROCESS_ITERATION) == null) {
                            // The acted upon vertex is the new containing process for the pid. 
                            // Excluding units from coming in here
                            pidSubjectUUID.put(actedUpon1.getAnnotation(OPMConstants.PROCESS_PID),
                                    getUuid(actedUpon1));
                        }

                    } else if (OPMConstants.WAS_GENERATED_BY.equals(edgeType)) {// 'mmap (write)' here too in case of MAP_ANONYMOUS

                        actingVertex = edgeForEvent.getParentVertex();
                        actedUpon1 = edgeForEvent.getChildVertex();

                    } else if (OPMConstants.USED.equals(edgeType)) {

                        actingVertex = edgeForEvent.getChildVertex();
                        actedUpon1 = edgeForEvent.getParentVertex();

                    } else {
                        logger.log(Level.WARNING, "Unexpected edge type {0}", new Object[] { edgeType });
                    }

                } else {
                    logger.log(Level.WARNING, "NULL edge for event {0}", new Object[] { eventType });
                }
            } else {

                AbstractEdge twoArtifactsEdge = getFirstMatchedEdge(edges, OPMConstants.TYPE,
                        OPMConstants.WAS_DERIVED_FROM);
                AbstractEdge edgeWithProcess = getFirstMatchedEdge(edges, OPMConstants.TYPE,
                        OPMConstants.WAS_GENERATED_BY);

                if (edges.size() == 2 || edges.size() == 3) {
                    // mmap(2 or 3 edges), rename(3), link(3), tee(3), splice(3)
                    if (twoArtifactsEdge != null && edgeWithProcess != null) {

                        // Add the protection to the WDF edge from the WGB edge (only for mmap)
                        if (twoArtifactsEdge.getAnnotation(OPMConstants.EDGE_OPERATION)
                                .equals(OPMConstants.OPERATION_MMAP)) {
                            twoArtifactsEdge.addAnnotation(OPMConstants.EDGE_PROTECTION,
                                    edgeWithProcess.getAnnotation(OPMConstants.EDGE_PROTECTION));
                        }

                        edgeForEvent = twoArtifactsEdge;
                        actedUpon1 = twoArtifactsEdge.getParentVertex();
                        actedUpon2 = twoArtifactsEdge.getChildVertex();
                        actingVertex = edgeWithProcess.getParentVertex();
                    } else {
                        logger.log(Level.WARNING, "Failed to process event with edges {0}", new Object[] { edges });
                    }
                } else {
                    logger.log(Level.WARNING, "Event with invalid number of edges {0}", new Object[] { edges });
                }
            }

            if (actingVertex != null) {

                publishEvent(eventType, edgeForEvent, actingVertex, actedUpon1, actedUpon2);

                // POST publishing things
                if (eventType.equals(EventType.EVENT_EXIT)) {
                    pidSubjectUUID.remove(actingVertex.getAnnotation(OPMConstants.PROCESS_PID));
                }

            } else {
                logger.log(Level.WARNING, "Null Process vertex for event with edges {0}", new Object[] { edges });
            }
        }
    }

    /**
     * Increment stats counts for the given key in the global map
     * 
     * @param key string
     */
    public void incrementStatsCount(String key) {
        if (stats.get(key) == null) {
            stats.put(key, 0L);
        }
        stats.put(key, stats.get(key) + 1);
    }

    /**
     * Creates a TCCDMDatum object with the given source and the value
     * @param value the CDM object instance
     * @param source the source value for that value
     * @return GenericContainer instance
     */
    private GenericContainer buildTcCDMDatum(Object value, InstrumentationSource source) {
        TCCDMDatum.Builder tccdmDatumBuilder = TCCDMDatum.newBuilder();
        tccdmDatumBuilder.setDatum(value);
        tccdmDatumBuilder.setSource(source);
        tccdmDatumBuilder.setHostId(hostUUID);
        tccdmDatumBuilder.setSessionNumber(sessionNumber);
        tccdmDatumBuilder.setType(getRecordType(value));
        return tccdmDatumBuilder.build();
    }

    private RecordType getRecordType(Object object) {
        RecordType type = null;
        String className = object.getClass().getSimpleName().toLowerCase();
        switch (className) {
        case "endmarker":
            type = RecordType.RECORD_END_MARKER;
            break;
        case "event":
            type = RecordType.RECORD_EVENT;
            break;
        case "fileobject":
            type = RecordType.RECORD_FILE_OBJECT;
            break;
        case "host":
            type = RecordType.RECORD_HOST;
            break;
        case "ipcobject":
            type = RecordType.RECORD_IPC_OBJECT;
            break;
        case "memoryobject":
            type = RecordType.RECORD_MEMORY_OBJECT;
            break;
        case "netflowobject":
            type = RecordType.RECORD_NET_FLOW_OBJECT;
            break;
        case "principal":
            type = RecordType.RECORD_PRINCIPAL;
            break;
        case "srcsinkobject":
            type = RecordType.RECORD_SRC_SINK_OBJECT;
            break;
        case "subject":
            type = RecordType.RECORD_SUBJECT;
            break;
        case "timemarker":
            type = RecordType.RECORD_TIME_MARKER;
            break;
        case "unitdependency":
            type = RecordType.RECORD_UNIT_DEPENDENCY;
            break;
        default:
            break;
        }
        return type;
    }

    /**
     * Returns the CDM object for the source annotation
     * Null if none matched
     * 
     * @param source allowed values listed in OPMConstants class
     * @return InstrumentationSource instance or null
     */
    private InstrumentationSource getInstrumentationSource(String source) {
        if (OPMConstants.SOURCE_AUDIT_SYSCALL.equals(source)) {
            return InstrumentationSource.SOURCE_LINUX_SYSCALL_TRACE;
        } else if (OPMConstants.SOURCE_AUDIT_NETFILTER.equals(source)) {
            return InstrumentationSource.SOURCE_LINUX_NETFILTER_TRACE;
        } else if (OPMConstants.SOURCE_PROCFS.equals(source)) {
            return InstrumentationSource.SOURCE_LINUX_PROC_TRACE;
        } else if (OPMConstants.SOURCE_BEEP.equals(source)) {
            return InstrumentationSource.SOURCE_LINUX_BEEP_TRACE;
        } else {
            logger.log(Level.WARNING, "Unexpected source: {0}", new Object[] { source });
        }
        return null;
    }

    /**
     * Converts the given time (in seconds) to nanoseconds
     * 
     * If failed to convert the given time for any reason then the default value is
     * returned.
     * 
     * @param eventId id of the event
     * @param time timestamp in seconds
     * @param defaultValue default value
     * @return the time in nanoseconds
     */
    private Long convertTimeToNanoseconds(Long eventId, String time, Long defaultValue) {
        try {
            if (time == null) {
                return defaultValue;
            }
            Double timeNanosecondsDouble = Double.parseDouble(time) * 1000; // Going to convert to long so multiply before
            Long timeNanosecondsLong = timeNanosecondsDouble.longValue();
            timeNanosecondsLong = timeNanosecondsLong * 1000 * 1000; //converting milliseconds to nanoseconds
            return timeNanosecondsLong;
        } catch (Exception e) {
            logger.log(Level.INFO, "Time type is not Double: {0}. event id = {1}", new Object[] { time, eventId });
            return defaultValue;
        }
    }

    /**
     * Adds the given key from the 'from' map to the 'to' map only if non-null
     * 
     * If either of the maps null then nothing happens
     * 
     * @param key key to check for and add as in the from and to maps respectively
     * @param from map to get the value for the key from
     * @param to map to put the value for the key and the key to
     */
    private void addIfNotNull(String key, Map<String, String> from, Map<CharSequence, CharSequence> to) {
        if (from != null && to != null) {
            if (from.get(key) != null) {
                to.put(key, from.get(key));
            }
        }
    }

    /**
     * Adds the value of fromkey from the 'from' map to the 'to' map as tokey only if non-null
     * 
     * If either of the maps null then nothing happens
     * 
     * @param fromKey key to check for and add as in the from map
     * @param from map to get the value for the key from
     * @param toKey key to put the value as in the to map
     * @param to map to put the value for the key and the key to
     */
    public void addIfNotNull(String fromKey, Map<String, String> from, String toKey,
            Map<CharSequence, CharSequence> to) {
        if (from != null && to != null) {
            if (from.get(fromKey) != null) {
                to.put(toKey, from.get(fromKey));
            }
        }
    }

    /**
     * Tests whether a vertex is a process vertex without throwing an NPE
     * 
     * @param process AbstractVertex
     * @return true if type is Process else false
     */
    private boolean isProcessVertex(AbstractVertex process) {
        return process != null && process.type().equals(OPMConstants.PROCESS);
    }

    /**
     * Creates a SimpleEntry object where the key is the time and the value is the
     * event id. 
     * 
     * Returns null if edge is null.
     * 
     * @param edge AbstractEdge
     * @return null/SimpleEntry object
     */
    private SimpleEntry<String, String> getTimeEventId(AbstractEdge edge) {
        if (edge != null) {
            return new SimpleEntry<String, String>(edge.getAnnotation(OPMConstants.EDGE_TIME),
                    edge.getAnnotation(OPMConstants.EDGE_EVENT_ID));
        }
        return null;
    }

    /**
     * Takes a list of edges and matches them against an array of key values.
     * Key values format. Every positive (and 0) index is the key and the next one
     * is the value
     * 
     * Returns the first one that matches all the annotations expected.
     * If a key is not found in the annotations of the edge then that edge is matched too
     * 
     * Returns null if edges are null or keysAndValues is null
     * 
     * @param edges list of AbstractEdges
     * @param keysAndValues annotation keys and their expected values
     * @return null / AbstractEdge
     */
    private AbstractEdge getFirstMatchedEdge(List<AbstractEdge> edges, String... keysAndValues) {
        if (edges != null && keysAndValues != null && keysAndValues.length % 2 == 0) {
            for (AbstractEdge edge : edges) {
                if (edge != null) {
                    boolean matched = true;
                    for (int a = 0; a < keysAndValues.length; a += 2) {
                        String edgeAnnotationValue = edge.getAnnotation(keysAndValues[a]);
                        if (edgeAnnotationValue != null) {
                            if (edgeAnnotationValue.equals(keysAndValues[a + 1])) {
                                matched = matched && true;
                            } else {
                                matched = matched && false;
                                break;
                            }
                        } else {
                            matched = matched && false;
                            break;
                        }
                    }
                    if (matched) {
                        return edge;
                    }
                }
            }
        }
        return null;
    }

    /**
     * Builds a Set of TypeOperation objects from a list of AbstractEdges by using the
     * 'type' and 'operation' annotations in the edge
     * 
     * @param edges list of edges
     * @return set of TypeOperation objects
     */
    private Set<TypeOperation> getTypeOperationSet(List<AbstractEdge> edges) {
        Set<TypeOperation> typesAndOperations = new HashSet<TypeOperation>();
        if (edges != null) {
            for (AbstractEdge edge : edges) {
                typesAndOperations.add(new TypeOperation(edge.getAnnotation(OPMConstants.TYPE),
                        edge.getAnnotation(OPMConstants.EDGE_OPERATION)));
            }
        }
        return typesAndOperations;
    }

    /**
     * Return EventType by first finding out the set of edge types and edge operations
     * from the list of edges and then looking that set in the rules map
     *     
     * @param edges list of edges
     * @return EventType enum value or null
     */
    private EventType getEventType(List<AbstractEdge> edges) {
        return rulesToEventType.get(getTypeOperationSet(edges));
    }

    private UUID getUUID(String str) {
        if (str != null) {
            byte[] hash = DigestUtils.md5(str);
            if (hexUUIDs) {
                hash = String.valueOf(Hex.encodeHex(hash, true)).getBytes();
            }
            return new UUID(hash);
        }
        return null;
    }

    private UUID getHostUUID(AbstractVertex vertex) {
        if (vertex != null) {
            String str = vertex.toString() + "," + sessionNumber;
            this.hostUUIDHex = Hex.encodeHexString(str.getBytes());
            return getUUID(str);
        }
        return null;
    }

    /**
     * Uses the bigHashCode function in AbstractVertex to get the hashcode which 
     * is then used to build the UUID object.
     * 
     * The bigHashCode is converted to hex values if {@link #hexUUIDs hexUUIDs} is true
     * 
     * Returns null if the vertex is null
     * 
     * @param vertex the vertex to calculate the hash of
     * @return null/UUID object
     */
    private UUID getUuid(AbstractVertex vertex) {
        if (vertex != null) {
            String str = vertex.toString() + "," + hostUUIDHex + "," + sessionNumber;
            return getUUID(str);
        }
        return null;
    }

    /**
     * Uses the bigHashCode function in AbstractEdge to get the hashcode which 
     * is then used to build the UUID object.
     * 
     * The bigHashCode is converted to hex values if {@link #hexUUIDs hexUUIDs} is true
     * 
     * Returns null if the edge is null
     * 
     * @param edge the edge to calculate the hash of
     * @return null/UUID object
     */
    private UUID getUuid(AbstractEdge edge) {
        if (edge != null) {
            String str = edge.toString() + "," + hostUUIDHex + "," + sessionNumber;
            return getUUID(str);
        }
        return null;
    }

    /**
     * Converts the permissions string to short first (using base 8) and then
     * adds writes the short to a bytebuffer and then gets the byte array from the
     * buffer which is then added to the CDM SHORT type
     * 
     * @param permissions octal permissions string
     * @return CDM SHORT type or null
     */
    private SHORT getPermissionsAsCDMSHORT(String permissions) {
        // IMPORTANT: If this function is changed then change the function in CDM reporter which reverses this
        if (permissions == null || permissions.isEmpty()) {
            return null;
        } else {
            Short permissionsShort = Short.parseShort(permissions, 8);
            ByteBuffer bb = ByteBuffer.allocate(2);
            bb.putShort(permissionsShort);
            SHORT cdmPermissions = new SHORT(bb.array());
            return cdmPermissions;
        }
    }
}

/**
 * A class to keep track of type and operation annotations on an OPM edge
 * 
 * hashCode and equals methods modified to add a special case when operation is '*'
 *
 */
class TypeOperation {
    public static final String ANY_OPERATION = "*";
    private String type, operation;

    public TypeOperation(String type, String operation) {
        this.type = type;
        this.operation = operation;
    }

    public String getType() {
        return type;
    }

    public String getOperation() {
        return operation;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        // Not using operation because it can have the special value -> '*'
        result = prime * result + ((type == null) ? 0 : type.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        TypeOperation other = (TypeOperation) obj;
        if (type == null) {
            if (other.type != null)
                return false;
        } else if (!type.equals(other.type))
            return false;
        if (ANY_OPERATION.equals(operation) || ANY_OPERATION.equals(other.operation)) {
            return true;
        }
        if (operation == null) {
            if (other.operation != null)
                return false;
        } else if (!operation.equals(other.operation))
            return false;
        return true;
    }

    @Override
    public String toString() {
        return "TypeOperation [type=" + type + ", operation=" + operation + "]";
    }
}