org.codice.alliance.nsili.common.ResultDAGConverter.java Source code

Java tutorial

Introduction

Here is the source code for org.codice.alliance.nsili.common.ResultDAGConverter.java

Source

/**
 * Copyright (c) Codice Foundation
 * <p>
 * This is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser
 * General Public License as published by the Free Software Foundation, either version 3 of the
 * License, or any later version.
 * <p>
 * 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
 * Lesser General Public License for more details. A copy of the GNU Lesser General Public License
 * is distributed along with this program and can be found at
 * <http://www.gnu.org/licenses/lgpl.html>.
 */
package org.codice.alliance.nsili.common;

import java.io.Serializable;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import org.apache.commons.lang.StringUtils;
import org.codice.alliance.nsili.common.UCO.AbsTime;
import org.codice.alliance.nsili.common.UCO.AbsTimeHelper;
import org.codice.alliance.nsili.common.UCO.DAG;
import org.codice.alliance.nsili.common.UCO.Edge;
import org.codice.alliance.nsili.common.UCO.Node;
import org.codice.alliance.nsili.common.UCO.NodeType;
import org.codice.alliance.nsili.common.UCO.Rectangle;
import org.codice.alliance.nsili.common.UCO.RectangleHelper;
import org.codice.alliance.nsili.common.UCO.Time;
import org.codice.alliance.nsili.common.UID.Product;
import org.codice.alliance.nsili.common.UID.ProductHelper;
import org.codice.ddf.configuration.SystemBaseUrl;
import org.codice.ddf.configuration.SystemInfo;
import org.jgrapht.event.ConnectedComponentTraversalEvent;
import org.jgrapht.event.EdgeTraversalEvent;
import org.jgrapht.event.TraversalListener;
import org.jgrapht.event.VertexTraversalEvent;
import org.jgrapht.experimental.dag.DirectedAcyclicGraph;
import org.jgrapht.traverse.DepthFirstIterator;
import org.omg.CORBA.Any;
import org.omg.CORBA.ORB;
import org.omg.PortableServer.POA;
import org.omg.PortableServer.POAPackage.ObjectAlreadyActive;
import org.omg.PortableServer.POAPackage.ServantAlreadyActive;
import org.omg.PortableServer.POAPackage.WrongPolicy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.io.ParseException;

import ddf.catalog.core.versioning.MetacardVersion;
import ddf.catalog.data.Attribute;
import ddf.catalog.data.Metacard;
import ddf.catalog.data.Result;
import ddf.catalog.data.types.Core;

public class ResultDAGConverter {
    private static final Logger LOGGER = LoggerFactory.getLogger(ResultDAGConverter.class);

    private static final String CATALOG_SOURCE_PATH = "/catalog/sources";

    private static final String THUMBNAIL_TRANSFORMER = "thumbnail";

    private static final String ENCODING = StandardCharsets.UTF_8.name();

    private static final Pattern ATTRIBUTE_PATTERN = Pattern
            .compile("([a-zA-Z0-9_:]+):([a-zA-Z0-9_]+).([a-zA-Z0-9]+)");

    public static DAG convertResult(Result result, ORB orb, POA poa, List<String> resultAttributes,
            Map<String, List<String>> mandatoryAttributes) throws DagParsingException {
        Double distanceInMeters = result.getDistanceInMeters();
        Double resultScore = result.getRelevanceScore();
        Metacard metacard = result.getMetacard();

        DAG dag = new DAG();
        DirectedAcyclicGraph<Node, Edge> graph = new DirectedAcyclicGraph<>(Edge.class);

        ProductImpl productImpl = new ProductImpl();

        String id = result.getMetacard().getId();

        if (!CorbaUtils.isIdActive(poa, id.getBytes(Charset.forName(ENCODING)))) {
            try {
                poa.activate_object_with_id(id.getBytes(Charset.forName(ENCODING)), productImpl);
            } catch (ServantAlreadyActive | ObjectAlreadyActive | WrongPolicy e) {
                LOGGER.info("Convert DAG : Unable to activate product impl object ({}): {}",
                        result.getMetacard().getId(), e.getLocalizedMessage());
            }
        }

        org.omg.CORBA.Object obj = poa.create_reference_with_id(id.getBytes(Charset.forName(ENCODING)),
                ProductHelper.id());
        Product product = ProductHelper.narrow(obj);

        Node productNode = createRootNode(orb);
        String attributeName = NsiliConstants.NSIL_PRODUCT;

        Any productAny = orb.create_any();
        ProductHelper.insert(productAny, product);
        productNode.value = productAny;

        graph.addVertex(productNode);

        List<String> addedAttributes = new ArrayList<>();
        addedAttributes.addAll(addCardNodeWithAttributes(graph, productNode, metacard, orb, attributeName + ":",
                resultAttributes));
        addedAttributes.addAll(addFileNodeWithAttributes(graph, productNode, metacard, orb, attributeName + ":",
                resultAttributes));
        addedAttributes.addAll(addSecurityNodeWithAttributes(graph, productNode, metacard, orb, attributeName + ":",
                resultAttributes));
        addedAttributes.addAll(addMetadataSecurityNodeWithAttributes(graph, productNode, metacard, orb,
                attributeName + ":", resultAttributes));
        addedAttributes.addAll(addParts(graph, productNode, metacard, orb, attributeName + ":", resultAttributes));

        if (metacard.getThumbnail() != null && metacard.getThumbnail().length > 0) {
            addedAttributes.addAll(addThumbnailRelatedFile(graph, productNode, metacard, orb, attributeName + ":",
                    resultAttributes));
        }

        if (mandatoryAttributes != null && !mandatoryAttributes.isEmpty()) {
            final ThreadLocal<Boolean> dataIsValid = new ThreadLocal<>();
            dataIsValid.set(true);
            Map<String, List<String>> addedAttrMap = getAttrMap(addedAttributes);
            addedAttrMap.entrySet().stream().forEach(entry -> dataIsValid.set(dataIsValid.get()
                    && processEntry(entry.getKey(), mandatoryAttributes.get(entry.getKey()), entry.getValue())));

            if (!dataIsValid.get()) {
                throw new DagParsingException("One or more mandatory attributes is missing on outgoing data");
            }
        }

        graph.addVertex(productNode);

        NsiliCommonUtils.setUCOEdgeIds(graph);
        NsiliCommonUtils.setUCOEdges(productNode, graph);
        dag.edges = NsiliCommonUtils.getEdgeArrayFromGraph(graph);
        dag.nodes = NsiliCommonUtils.getNodeArrayFromGraph(graph);

        return dag;
    }

    public static List<String> addCardNodeWithAttributes(DirectedAcyclicGraph<Node, Edge> graph, Node productNode,
            Metacard metacard, ORB orb, String parentAttrName, List<String> resultAttributes) {
        List<String> addedAttributes = new ArrayList<>();
        Any any = orb.create_any();
        Node cardNode = new Node(0, NodeType.ENTITY_NODE, NsiliConstants.NSIL_CARD, any);
        graph.addVertex(cardNode);
        graph.addEdge(productNode, cardNode);

        String attribute = parentAttrName + NsiliConstants.NSIL_CARD;

        if (shouldAdd(buildAttr(attribute, NsiliConstants.IDENTIFIER), resultAttributes)
                && metacard.getId() != null) {
            addStringAttribute(graph, cardNode, NsiliConstants.IDENTIFIER, metacard.getId(), orb);
            addedAttributes.add(buildAttr(attribute, NsiliConstants.IDENTIFIER));
        }

        if (metacard.getCreatedDate() != null) {
            if (shouldAdd(buildAttr(attribute, NsiliConstants.SOURCE_DATE_TIME_MODIFIED), resultAttributes)) {
                addDateAttribute(graph, cardNode, NsiliConstants.SOURCE_DATE_TIME_MODIFIED,
                        metacard.getCreatedDate(), orb);
                addedAttributes.add(buildAttr(attribute, NsiliConstants.SOURCE_DATE_TIME_MODIFIED));
            }

            if (shouldAdd(buildAttr(attribute, NsiliConstants.DATE_TIME_MODIFIED), resultAttributes)) {
                addDateAttribute(graph, cardNode, NsiliConstants.DATE_TIME_MODIFIED, metacard.getCreatedDate(),
                        orb);
                addedAttributes.add(buildAttr(attribute, NsiliConstants.DATE_TIME_MODIFIED));
            }
        }

        if (shouldAdd(buildAttr(attribute, NsiliConstants.SOURCE_LIBRARY), resultAttributes)) {
            if (StringUtils.isNotBlank(metacard.getSourceId())) {
                addStringAttribute(graph, cardNode, NsiliConstants.SOURCE_LIBRARY, metacard.getSourceId(), orb);
            } else {
                addStringAttribute(graph, cardNode, NsiliConstants.SOURCE_LIBRARY, NsiliConstants.UNKNOWN, orb);
            }
            addedAttributes.add(buildAttr(attribute, NsiliConstants.SOURCE_LIBRARY));
        }

        if (shouldAdd(buildAttr(attribute, NsiliConstants.STATUS), resultAttributes)) {
            String status = NsiliCardStatus.CHANGED.name();
            Attribute createdAttr = metacard.getAttribute(Core.METACARD_CREATED);
            Attribute modifiedAttr = metacard.getAttribute(Core.METACARD_MODIFIED);
            if (createdAttr != null && modifiedAttr != null) {
                Date createdDate = (Date) createdAttr.getValue();
                Date modifiedDate = (Date) modifiedAttr.getValue();
                if (createdDate.equals(modifiedDate)) {
                    status = NsiliCardStatus.NEW.name();
                }
            }

            Attribute versionAction = metacard.getAttribute(MetacardVersion.ACTION);

            if (versionAction != null) {
                for (Serializable action : versionAction.getValues()) {
                    if (action.toString().trim().equals(MetacardVersion.Action.DELETED.getKey())) {
                        status = NsiliCardStatus.OBSOLETE.name();
                        break;
                    }
                }
            }

            addStringAttribute(graph, cardNode, NsiliConstants.STATUS, status, orb);
            addedAttributes.add(buildAttr(attribute, NsiliConstants.STATUS));
        }

        return addedAttributes;
    }

    public static List<String> addFileNodeWithAttributes(DirectedAcyclicGraph<Node, Edge> graph, Node productNode,
            Metacard metacard, ORB orb, String parentAttrName, List<String> resultAttributes) {
        List<String> addedAttributes = new ArrayList<>();
        Any any = orb.create_any();
        Node fileNode = new Node(0, NodeType.ENTITY_NODE, NsiliConstants.NSIL_FILE, any);
        graph.addVertex(fileNode);
        graph.addEdge(productNode, fileNode);

        String attribute = parentAttrName + NsiliConstants.NSIL_FILE;

        if (shouldAdd(buildAttr(attribute, NsiliConstants.ARCHIVED), resultAttributes)) {
            addBooleanAttribute(graph, fileNode, NsiliConstants.ARCHIVED, false, orb);
            addedAttributes.add(buildAttr(attribute, NsiliConstants.ARCHIVED));
        }

        if (shouldAdd(buildAttr(attribute, NsiliConstants.CREATOR), resultAttributes)) {
            Attribute pocAttr = metacard.getAttribute(Metacard.POINT_OF_CONTACT);
            if (pocAttr != null) {
                String pocString = String.valueOf(pocAttr.getValue());
                if (StringUtils.isNotBlank(pocString)) {
                    addStringAttribute(graph, fileNode, NsiliConstants.CREATOR, pocString, orb);
                } else {
                    addStringAttribute(graph, fileNode, NsiliConstants.CREATOR, SystemInfo.getSiteName(), orb);
                }
            } else {
                addStringAttribute(graph, fileNode, NsiliConstants.CREATOR, SystemInfo.getSiteName(), orb);
            }
            addedAttributes.add(buildAttr(attribute, NsiliConstants.CREATOR));
        }

        if (shouldAdd(buildAttr(attribute, NsiliConstants.EXTENT), resultAttributes)) {
            if (metacard.getResourceSize() != null) {
                try {
                    Double resSize = Double.valueOf(metacard.getResourceSize());
                    Double resSizeMB = convertToMegabytes(resSize);
                    if (resSizeMB != null) {
                        addDoubleAttribute(graph, fileNode, NsiliConstants.EXTENT, resSizeMB, orb);
                        addedAttributes.add(buildAttr(attribute, NsiliConstants.EXTENT));
                    }
                } catch (NumberFormatException nfe) {
                    LOGGER.warn("Couldn't convert the resource size to double: {}", metacard.getResourceSize());
                }
            }
        }

        if (shouldAdd(buildAttr(attribute, NsiliConstants.FORMAT), resultAttributes)
                && metacard.getContentTypeName() != null) {
            addStringAttribute(graph, fileNode, NsiliConstants.FORMAT, metacard.getContentTypeName(), orb);
            addedAttributes.add(buildAttr(attribute, NsiliConstants.FORMAT));
        }

        if (shouldAdd(buildAttr(attribute, NsiliConstants.FORMAT_VERSION), resultAttributes)
                && metacard.getContentTypeVersion() != null) {
            addStringAttribute(graph, fileNode, NsiliConstants.FORMAT_VERSION, metacard.getContentTypeVersion(),
                    orb);
            addedAttributes.add(buildAttr(attribute, NsiliConstants.FORMAT_VERSION));
        }

        if (shouldAdd(buildAttr(attribute, NsiliConstants.PRODUCT_URL), resultAttributes)) {
            Attribute downloadUrlAttr = metacard.getAttribute(Metacard.RESOURCE_DOWNLOAD_URL);
            if (downloadUrlAttr != null) {
                String downloadUrl = String.valueOf(downloadUrlAttr.getValue());
                if (downloadUrl != null) {
                    downloadUrl = modifyUrl(downloadUrl, metacard.getTitle());
                    addStringAttribute(graph, fileNode, NsiliConstants.PRODUCT_URL, downloadUrl, orb);
                    addedAttributes.add(buildAttr(attribute, NsiliConstants.PRODUCT_URL));
                }
            }
        }

        if (shouldAdd(buildAttr(attribute, NsiliConstants.TITLE), resultAttributes)
                && metacard.getTitle() != null) {
            addStringAttribute(graph, fileNode, NsiliConstants.TITLE, metacard.getTitle(), orb);
            addedAttributes.add(buildAttr(attribute, NsiliConstants.TITLE));
        }

        if (shouldAdd(buildAttr(attribute, NsiliConstants.IS_PRODUCT_LOCAL), resultAttributes)) {
            String siteName = SystemInfo.getSiteName();
            boolean productLocal = true;
            if (siteName != null && metacard.getSourceId() != null && !siteName.equals(metacard.getSourceId())) {
                productLocal = false;
            }
            addBooleanAttribute(graph, fileNode, NsiliConstants.IS_PRODUCT_LOCAL, productLocal, orb);
            addedAttributes.add(buildAttr(attribute, NsiliConstants.IS_PRODUCT_LOCAL));
        }

        if (shouldAdd(buildAttr(attribute, NsiliConstants.DATE_TIME_DECLARED), resultAttributes)) {
            if (metacard.getCreatedDate() != null) {
                addDateAttribute(graph, fileNode, NsiliConstants.DATE_TIME_DECLARED, metacard.getCreatedDate(),
                        orb);
            } else {
                addDateAttribute(graph, fileNode, NsiliConstants.DATE_TIME_DECLARED, new Date(), orb);
            }
            addedAttributes.add(buildAttr(attribute, NsiliConstants.DATE_TIME_DECLARED));
        }
        return addedAttributes;
    }

    public static List<String> addSecurityNodeWithAttributes(DirectedAcyclicGraph<Node, Edge> graph,
            Node productNode, Metacard metacard, ORB orb, String parentAttrName, List<String> resultAttributes) {
        List<String> addedAttributes = new ArrayList<>();
        Any any = orb.create_any();
        Node securityNode = new Node(0, NodeType.ENTITY_NODE, NsiliConstants.NSIL_SECURITY, any);
        graph.addVertex(securityNode);
        graph.addEdge(productNode, securityNode);

        String attribute = parentAttrName + NsiliConstants.NSIL_SECURITY;

        //TODO -- Once taxonomy is complete, set real values
        if (shouldAdd(buildAttr(attribute, NsiliConstants.CLASSIFICATION), resultAttributes)) {
            addStringAttribute(graph, securityNode, NsiliConstants.CLASSIFICATION,
                    NsiliClassification.UNCLASSIFIED.getSpecName(), orb);
            addedAttributes.add(buildAttr(attribute, NsiliConstants.CLASSIFICATION));
        }

        if (shouldAdd(buildAttr(attribute, NsiliConstants.POLICY), resultAttributes)) {
            addStringAttribute(graph, securityNode, NsiliConstants.POLICY, "NATO", orb);
            addedAttributes.add(buildAttr(attribute, NsiliConstants.POLICY));
        }

        if (shouldAdd(buildAttr(attribute, NsiliConstants.RELEASABILITY), resultAttributes)) {
            addStringAttribute(graph, securityNode, NsiliConstants.RELEASABILITY, "NATO", orb);
            addedAttributes.add(buildAttr(attribute, NsiliConstants.RELEASABILITY));
        }
        return addedAttributes;
    }

    public static List<String> addMetadataSecurityNodeWithAttributes(DirectedAcyclicGraph<Node, Edge> graph,
            Node productNode, Metacard metacard, ORB orb, String parentAttrName, List<String> resultAttributes) {
        List<String> addedAttributes = new ArrayList<>();
        Any any = orb.create_any();
        Node metadataSecurityNode = new Node(0, NodeType.ENTITY_NODE, NsiliConstants.NSIL_METADATA_SECURITY, any);
        graph.addVertex(metadataSecurityNode);
        graph.addEdge(productNode, metadataSecurityNode);

        String attribute = parentAttrName + NsiliConstants.NSIL_METADATA_SECURITY;

        //TODO -- Once taxonomy is complete, set real values
        if (shouldAdd(buildAttr(attribute, NsiliConstants.CLASSIFICATION), resultAttributes)) {
            addStringAttribute(graph, metadataSecurityNode, NsiliConstants.CLASSIFICATION,
                    NsiliClassification.UNCLASSIFIED.getSpecName(), orb);
            addedAttributes.add(buildAttr(attribute, NsiliConstants.CLASSIFICATION));
        }

        if (shouldAdd(buildAttr(attribute, NsiliConstants.POLICY), resultAttributes)) {
            addStringAttribute(graph, metadataSecurityNode, NsiliConstants.POLICY, "NATO", orb);
            addedAttributes.add(buildAttr(attribute, NsiliConstants.POLICY));
        }

        if (shouldAdd(buildAttr(attribute, NsiliConstants.RELEASABILITY), resultAttributes)) {
            addStringAttribute(graph, metadataSecurityNode, NsiliConstants.RELEASABILITY, "NATO", orb);
            addedAttributes.add(buildAttr(attribute, NsiliConstants.RELEASABILITY));
        }
        return addedAttributes;
    }

    public static List<String> addParts(DirectedAcyclicGraph<Node, Edge> graph, Node productNode, Metacard metacard,
            ORB orb, String parentAttrName, List<String> resultAttributes) {
        List<String> addedAttributes = new ArrayList<>();
        Any any = orb.create_any();
        Node partNode = new Node(0, NodeType.ENTITY_NODE, NsiliConstants.NSIL_PART, any);
        graph.addVertex(partNode);
        graph.addEdge(productNode, partNode);

        String attribute = parentAttrName + NsiliConstants.NSIL_PART;

        //Determine if more than one part specific view is associated with data in this metacard
        boolean partAdded = false;
        String type = null;

        String partIdentifier = "1";
        if (shouldAdd(buildAttr(attribute, NsiliConstants.PART_IDENTIFIER), resultAttributes)) {
            addStringAttribute(graph, partNode, NsiliConstants.PART_IDENTIFIER, partIdentifier, orb);
            addedAttributes.add(buildAttr(attribute, NsiliConstants.PART_IDENTIFIER));
        }

        addedAttributes.addAll(
                addSecurityNodeWithAttributes(graph, partNode, metacard, orb, attribute + ":", resultAttributes));
        addedAttributes.addAll(
                addCoverageNodeWithAttributes(graph, partNode, metacard, orb, attribute + ":", resultAttributes));

        if (metacardContainsImageryData(metacard)) {
            type = NsiliProductType.IMAGERY.getSpecName();

            addedAttributes.addAll(addImageryPart(graph, partNode, metacard, type, orb, partIdentifier,
                    attribute + ":", resultAttributes));
        }

        if (metacardContainsGmtiData(metacard)) {
            type = NsiliProductType.GMTI.getSpecName();
            if (partAdded) {

            } else {

            }
        }

        if (metacardContainsMessageData(metacard)) {
            type = NsiliProductType.MESSAGE.getSpecName();
            if (partAdded) {

            } else {

            }
        }

        if (metacardContainsReportData(metacard)) {
            type = NsiliProductType.REPORT.getSpecName();
            if (partAdded) {

            } else {

            }
        }

        if (metacardContainsTdlData(metacard)) {
            type = NsiliProductType.TDL_DATA.getSpecName();
            if (partAdded) {

            } else {

            }
        }

        if (metacardContainsVideoData(metacard)) {
            type = NsiliProductType.VIDEO.getSpecName();
            if (partAdded) {

            } else {

            }
        }

        addedAttributes.addAll(addCommonNodeWithAttributes(graph, partNode, metacard, type, orb, attribute + ":",
                resultAttributes));

        return addedAttributes;
    }

    public static List<String> addImageryPart(DirectedAcyclicGraph<Node, Edge> graph, Node partNode,
            Metacard metacard, String type, ORB orb, String partIdentifier, String parentAttrName,
            List<String> resultAttributes) {
        List<String> addedAttributes = new ArrayList<>();
        Any imageryAny = orb.create_any();
        Node imageryNode = new Node(0, NodeType.ENTITY_NODE, NsiliConstants.NSIL_IMAGERY, imageryAny);
        graph.addVertex(imageryNode);
        graph.addEdge(partNode, imageryNode);

        String attribute = parentAttrName + NsiliConstants.NSIL_IMAGERY;

        //TODO -- Once taxonomy is complete, set real values
        if (shouldAdd(buildAttr(attribute, NsiliConstants.CATEGORY), resultAttributes)) {
            addStringAttribute(graph, imageryNode, NsiliConstants.CATEGORY, NsiliImageryType.VIS.name(), orb);
            addedAttributes.add(buildAttr(attribute, NsiliConstants.CATEGORY));
        }

        if (shouldAdd(buildAttr(attribute, NsiliConstants.DECOMPRESSION_TECHNIQUE), resultAttributes)) {
            addStringAttribute(graph, imageryNode, NsiliConstants.DECOMPRESSION_TECHNIQUE,
                    NsiliImageryDecompressionTech.NC.name(), orb);
            addedAttributes.add(buildAttr(attribute, NsiliConstants.DECOMPRESSION_TECHNIQUE));
        }

        if (shouldAdd(buildAttr(attribute, NsiliConstants.IDENTIFIER), resultAttributes)) {
            addStringAttribute(graph, imageryNode, NsiliConstants.IDENTIFIER, partIdentifier, orb);
            addedAttributes.add(buildAttr(attribute, NsiliConstants.IDENTIFIER));
        }

        if (shouldAdd(buildAttr(attribute, NsiliConstants.NUMBER_OF_BANDS), resultAttributes)) {
            addIntegerAttribute(graph, imageryNode, NsiliConstants.NUMBER_OF_BANDS, 1, orb);
            addedAttributes.add(buildAttr(attribute, NsiliConstants.NUMBER_OF_BANDS));
        }

        return addedAttributes;
    }

    public static List<String> addCommonNodeWithAttributes(DirectedAcyclicGraph<Node, Edge> graph, Node partNode,
            Metacard metacard, String type, ORB orb, String parentAttrName, List<String> resultAttributes) {
        List<String> addedAttributes = new ArrayList<>();
        Any any = orb.create_any();
        Node commonNode = new Node(0, NodeType.ENTITY_NODE, NsiliConstants.NSIL_COMMON, any);
        graph.addVertex(commonNode);
        graph.addEdge(partNode, commonNode);

        String attribute = parentAttrName + NsiliConstants.NSIL_COMMON;

        if (shouldAdd(buildAttr(attribute, NsiliConstants.DESCRIPTION_ABSTRACT), resultAttributes)) {
            Attribute descAttr = metacard.getAttribute(Metacard.DESCRIPTION);
            if (descAttr != null) {
                String descString = String.valueOf(descAttr.getValue());
                if (descString != null) {
                    addStringAttribute(graph, commonNode, NsiliConstants.DESCRIPTION_ABSTRACT, descString, orb);
                    addedAttributes.add(buildAttr(attribute, NsiliConstants.DESCRIPTION_ABSTRACT));
                }
            }
        }

        if (shouldAdd(buildAttr(attribute, NsiliConstants.IDENTIFIER_UUID), resultAttributes)) {
            String metacardId = getMetacardId(metacard);
            if (metacardId != null) {
                UUID uuid = getUUIDFromCard(metacardId);
                addStringAttribute(graph, commonNode, NsiliConstants.IDENTIFIER_UUID, uuid.toString(), orb);
                addedAttributes.add(buildAttr(attribute, NsiliConstants.IDENTIFIER_UUID));
            }
        }

        if (shouldAdd(buildAttr(attribute, NsiliConstants.TYPE), resultAttributes) && type != null) {
            addStringAttribute(graph, commonNode, NsiliConstants.TYPE, type, orb);
            addedAttributes.add(buildAttr(attribute, NsiliConstants.TYPE));
        }

        return addedAttributes;
    }

    public static List<String> addCoverageNodeWithAttributes(DirectedAcyclicGraph<Node, Edge> graph, Node partNode,
            Metacard metacard, ORB orb, String parentAttrName, List<String> resultAttributes) {
        List<String> addedAttributes = new ArrayList<>();
        Any any = orb.create_any();

        if (metacardContainsGeoInfo(metacard)) {
            Node coverageNode = new Node(0, NodeType.ENTITY_NODE, NsiliConstants.NSIL_COVERAGE, any);
            graph.addVertex(coverageNode);
            graph.addEdge(partNode, coverageNode);

            String attribute = parentAttrName + NsiliConstants.NSIL_COVERAGE;

            if (shouldAdd(buildAttr(attribute, NsiliConstants.ADVANCED_GEOSPATIAL), resultAttributes)) {
                Attribute geoAttr = metacard.getAttribute(Metacard.GEOGRAPHY);
                if (geoAttr != null) {
                    String wktGeo = String.valueOf(geoAttr.getValue());
                    addStringAttribute(graph, coverageNode, NsiliConstants.ADVANCED_GEOSPATIAL, wktGeo, orb);
                    addedAttributes.add(buildAttr(attribute, NsiliConstants.ADVANCED_GEOSPATIAL));
                }
            }

            if (shouldAdd(buildAttr(attribute, NsiliConstants.SPATIAL_GEOGRAPHIC_REF_BOX), resultAttributes)) {
                Attribute geoAttr = metacard.getAttribute(Metacard.GEOGRAPHY);
                if (geoAttr != null) {
                    String wktGeo = String.valueOf(geoAttr.getValue());
                    try {
                        Geometry boundingGeo = WKTUtil.getWKTBoundingRectangle(wktGeo);
                        Rectangle rect = NsiliGeomUtil.getRectangle(boundingGeo);
                        addGeomAttribute(graph, coverageNode, NsiliConstants.SPATIAL_GEOGRAPHIC_REF_BOX, rect, orb);
                        addedAttributes.add(buildAttr(attribute, NsiliConstants.SPATIAL_GEOGRAPHIC_REF_BOX));
                    } catch (ParseException pe) {
                        LOGGER.warn("Unable to parse WKT for bounding box: {}", wktGeo, pe);
                    }
                }
            }
        }
        return addedAttributes;
    }

    public static List<String> addThumbnailRelatedFile(DirectedAcyclicGraph<Node, Edge> graph, Node productNode,
            Metacard metacard, ORB orb, String parentAttrName, List<String> resultAttributes) {
        List<String> addedAttributes = new ArrayList<>();
        Any any = orb.create_any();
        Node relatedFileNode = new Node(0, NodeType.ENTITY_NODE, NsiliConstants.NSIL_RELATED_FILE, any);
        graph.addVertex(relatedFileNode);
        graph.addEdge(productNode, relatedFileNode);

        String attribute = parentAttrName + NsiliConstants.NSIL_RELATED_FILE;

        if (shouldAdd(buildAttr(attribute, NsiliConstants.CREATOR), resultAttributes)) {
            Attribute pocAttr = metacard.getAttribute(Metacard.POINT_OF_CONTACT);
            if (pocAttr != null) {
                String pocString = String.valueOf(pocAttr.getValue());
                if (StringUtils.isNotBlank(pocString)) {
                    addStringAttribute(graph, relatedFileNode, NsiliConstants.CREATOR, pocString, orb);
                } else {
                    addStringAttribute(graph, relatedFileNode, NsiliConstants.CREATOR, SystemInfo.getSiteName(),
                            orb);
                }
            } else {
                addStringAttribute(graph, relatedFileNode, NsiliConstants.CREATOR, SystemInfo.getSiteName(), orb);
            }
            addedAttributes.add(buildAttr(attribute, NsiliConstants.CREATOR));
        }

        if (shouldAdd(buildAttr(attribute, NsiliConstants.DATE_TIME_DECLARED), resultAttributes)) {
            if (metacard.getCreatedDate() != null) {
                addDateAttribute(graph, relatedFileNode, NsiliConstants.DATE_TIME_DECLARED,
                        metacard.getCreatedDate(), orb);
            } else {
                addDateAttribute(graph, relatedFileNode, NsiliConstants.DATE_TIME_DECLARED, new Date(), orb);
            }
            addedAttributes.add(buildAttr(attribute, NsiliConstants.DATE_TIME_DECLARED));
        }

        if (shouldAdd(buildAttr(attribute, NsiliConstants.EXTENT), resultAttributes)) {
            if (metacard.getThumbnail() != null) {
                try {
                    Double resSize = (double) metacard.getThumbnail().length;
                    Double resSizeMB = convertToMegabytes(resSize);
                    addDoubleAttribute(graph, relatedFileNode, NsiliConstants.EXTENT, resSizeMB, orb);
                    addedAttributes.add(buildAttr(attribute, NsiliConstants.EXTENT));
                } catch (NumberFormatException nfe) {
                    LOGGER.warn("Couldn't convert the thumbnail size to double: {}", metacard.getResourceSize());
                }
            }
        }

        if (shouldAdd(buildAttr(attribute, NsiliConstants.URL), resultAttributes)) {
            try {
                String thumbnailURL = new URI(
                        SystemBaseUrl.constructUrl(CATALOG_SOURCE_PATH + "/" + metacard.getSourceId() + "/"
                                + metacard.getId() + "?transform=" + THUMBNAIL_TRANSFORMER, true)).toASCIIString();
                addStringAttribute(graph, relatedFileNode, NsiliConstants.URL, thumbnailURL, orb);
                addedAttributes.add(buildAttr(attribute, NsiliConstants.URL));
            } catch (URISyntaxException e) {
                LOGGER.warn("Unable to construct URI: {}", e);
                LOGGER.debug("", e);
            }
        }

        if (shouldAdd(buildAttr(attribute, NsiliConstants.IS_FILE_LOCAL), resultAttributes)) {
            String siteName = SystemInfo.getSiteName();

            boolean fileLocal = true;
            if (siteName != null && metacard.getSourceId() != null && !siteName.equals(metacard.getSourceId())) {
                fileLocal = false;
            }
            addBooleanAttribute(graph, relatedFileNode, NsiliConstants.IS_FILE_LOCAL, fileLocal, orb);
            addedAttributes.add(buildAttr(attribute, NsiliConstants.IS_FILE_LOCAL));
        }

        if (shouldAdd(buildAttr(attribute, NsiliConstants.FILE_TYPE), resultAttributes)) {
            addStringAttribute(graph, relatedFileNode, NsiliConstants.FILE_TYPE, NsiliConstants.THUMBNAIL_TYPE,
                    orb);
            addedAttributes.add(buildAttr(attribute, NsiliConstants.FILE_TYPE));
        }

        return addedAttributes;
    }

    public static Node createRootNode(ORB orb) {
        return new Node(0, NodeType.ROOT_NODE, NsiliConstants.NSIL_PRODUCT, orb.create_any());
    }

    public static void addStringAttribute(DirectedAcyclicGraph<Node, Edge> graph, Node parentNode, String key,
            String value, ORB orb) {
        Any any = orb.create_any();
        any.insert_string(value);
        Node node = new Node(0, NodeType.ATTRIBUTE_NODE, key, any);
        graph.addVertex(node);
        graph.addEdge(parentNode, node);
    }

    public static void addIntegerAttribute(DirectedAcyclicGraph<Node, Edge> graph, Node parentNode, String key,
            Integer integer, ORB orb) {
        Any any = orb.create_any();
        any.insert_ulong(integer);
        Node node = new Node(0, NodeType.ATTRIBUTE_NODE, key, any);
        graph.addVertex(node);
        graph.addEdge(parentNode, node);
    }

    public static void addShortAttribute(DirectedAcyclicGraph<Node, Edge> graph, Node parentNode, String key,
            Short shortVal, ORB orb) {
        Any any = orb.create_any();
        any.insert_short(shortVal);
        Node node = new Node(0, NodeType.ATTRIBUTE_NODE, key, any);
        graph.addVertex(node);
        graph.addEdge(parentNode, node);
    }

    public static void addDoubleAttribute(DirectedAcyclicGraph<Node, Edge> graph, Node parentNode, String key,
            Double doubleVal, ORB orb) {
        Any any = orb.create_any();
        any.insert_double(doubleVal);
        Node node = new Node(0, NodeType.ATTRIBUTE_NODE, key, any);
        graph.addVertex(node);
        graph.addEdge(parentNode, node);
    }

    public static void addBooleanAttribute(DirectedAcyclicGraph<Node, Edge> graph, Node parentNode, String key,
            Boolean boolVal, ORB orb) {
        Any any = orb.create_any();
        any.insert_boolean(boolVal);
        Node node = new Node(0, NodeType.ATTRIBUTE_NODE, key, any);
        graph.addVertex(node);
        graph.addEdge(parentNode, node);
    }

    public static void addAnyAttribute(DirectedAcyclicGraph<Node, Edge> graph, Node parentNode, String key, Any any,
            ORB orb) {
        Node node = new Node(0, NodeType.ATTRIBUTE_NODE, key, any);
        graph.addVertex(node);
        graph.addEdge(parentNode, node);
    }

    public static void addDateAttribute(DirectedAcyclicGraph<Node, Edge> graph, Node parentNode, String key,
            Date date, ORB orb) {
        Any any = orb.create_any();
        AbsTimeHelper.insert(any, getAbsTime(date));
        Node node = new Node(0, NodeType.ATTRIBUTE_NODE, key, any);
        graph.addVertex(node);
        graph.addEdge(parentNode, node);
    }

    public static void addGeomAttribute(DirectedAcyclicGraph<Node, Edge> graph, Node parentNode, String key,
            Rectangle rectangle, ORB orb) {
        Any any = orb.create_any();
        RectangleHelper.insert(any, rectangle);
        Node node = new Node(0, NodeType.ATTRIBUTE_NODE, key, any);
        graph.addVertex(node);
        graph.addEdge(parentNode, node);
    }

    public static Double convertToMegabytes(Double resSizeBytes) {
        if (resSizeBytes != null) {
            return resSizeBytes / (1024 * 1024);
        } else {
            return null;
        }
    }

    public static AbsTime getAbsTime(Date date) {
        Calendar cal = new GregorianCalendar();
        cal.setTime(date);

        return new AbsTime(
                new org.codice.alliance.nsili.common.UCO.Date((short) cal.get(Calendar.YEAR),
                        (short) (cal.get(Calendar.MONTH) + 1), (short) cal.get(Calendar.DAY_OF_MONTH)),
                new Time((short) cal.get(Calendar.HOUR_OF_DAY), (short) cal.get(Calendar.MINUTE),
                        (short) cal.get(Calendar.SECOND)));
    }

    public static Map<Integer, Node> createNodeMap(Node[] nodes) {
        Map<Integer, Node> nodeMap = new HashMap<>();
        for (Node node : nodes) {
            nodeMap.put(node.id, node);
        }

        return nodeMap;
    }

    public static List<String> getAttributes(DAG dag) {
        return new ArrayList<>(getAttributeMap(dag).keySet());
    }

    public static Map<String, String> getAttributeMap(DAG dag) {
        Map<String, String> attributes = new HashMap<>();

        Map<Integer, Node> nodeMap = createNodeMap(dag.nodes);
        DirectedAcyclicGraph<Node, Edge> graph = new DirectedAcyclicGraph<>(Edge.class);

        for (Node node : dag.nodes) {
            graph.addVertex(node);
        }

        for (Edge edge : dag.edges) {
            Node node1 = nodeMap.get(edge.start_node);
            Node node2 = nodeMap.get(edge.end_node);
            if (node1 != null && node2 != null) {
                graph.addEdge(node1, node2);
            }
        }

        DepthFirstIterator<Node, Edge> graphIT = new DepthFirstIterator<>(graph, nodeMap.get(0));
        List<String> nodeStack = new ArrayList<>();

        graphIT.addTraversalListener(new TraversalListener<Node, Edge>() {
            @Override
            public void connectedComponentFinished(
                    ConnectedComponentTraversalEvent connectedComponentTraversalEvent) {

            }

            @Override
            public void connectedComponentStarted(
                    ConnectedComponentTraversalEvent connectedComponentTraversalEvent) {

            }

            @Override
            public void edgeTraversed(EdgeTraversalEvent<Node, Edge> edgeTraversalEvent) {

            }

            @Override
            public void vertexTraversed(VertexTraversalEvent<Node> vertexTraversalEvent) {
                Node node = vertexTraversalEvent.getVertex();
                if (node.node_type != NodeType.ATTRIBUTE_NODE) {
                    nodeStack.add(node.attribute_name);
                }
            }

            @Override
            public void vertexFinished(VertexTraversalEvent<Node> vertexTraversalEvent) {
                Node node = vertexTraversalEvent.getVertex();
                if (node.node_type == NodeType.ATTRIBUTE_NODE) {
                    String attribute = "";
                    int currEntry = 0;
                    int size = nodeStack.size();
                    for (String nodeEntry : nodeStack) {
                        attribute += nodeEntry;
                        if (currEntry < (size - 1)) {
                            attribute += ":";
                        } else {
                            attribute += ".";
                        }
                        currEntry++;
                    }
                    attribute += node.attribute_name;
                    attributes.put(attribute, CorbaUtils.getNodeValue(node.value));
                } else {
                    int lastIdx = nodeStack.size() - 1;
                    nodeStack.remove(lastIdx);
                }
            }
        });

        Node rootNode = null;
        while (graphIT.hasNext()) {
            graphIT.setCrossComponentTraversal(false);

            Node node = graphIT.next();
            if (rootNode == null) {
                rootNode = node;
            }
        }

        return attributes;
    }

    private static boolean metacardContainsImageryData(Metacard metacard) {
        //TODO Implement
        return true;
    }

    private static boolean metacardContainsGmtiData(Metacard metacard) {
        //TODO implement
        return false;
    }

    private static boolean metacardContainsMessageData(Metacard metacard) {
        //TODO implement
        return false;
    }

    private static boolean metacardContainsReportData(Metacard metacard) {
        //TODO implement
        return false;
    }

    private static boolean metacardContainsTdlData(Metacard metacard) {
        //TODO implement
        return false;
    }

    private static boolean metacardContainsVideoData(Metacard metacard) {
        //TODO implement
        return false;
    }

    private static boolean metacardContainsGeoInfo(Metacard metacard) {
        boolean foundGeoInfo = false;

        if (metacard.getAttribute(Metacard.GEOGRAPHY) != null) {
            foundGeoInfo = true;
        }

        return foundGeoInfo;
    }

    private static String modifyUrl(String url, String name) {
        return url + "&nsiliFilename=" + name;
    }

    private static UUID getUUIDFromCard(String id) {
        UUID uuid = null;
        try {
            if (id.contains("-")) {
                uuid = UUID.fromString(id);
            } else if (id.length() == 32) {
                //Attempt to parse as a UUID with no dashes
                StringBuilder sb = new StringBuilder(id);
                sb.insert(8, "-");
                sb.insert(13, "-");
                sb.insert(18, "-");
                sb.insert(23, "-");
                uuid = UUID.fromString(sb.toString());
            }
        } catch (Exception e) {
        }

        //If parsing fails, get a v3 UUID from the bytes of the metacard ID
        if (uuid == null) {
            uuid = UUID.nameUUIDFromBytes(id.getBytes());
        }

        return uuid;
    }

    private static boolean shouldAdd(String attributeName, List<String> resultAttributes) {
        boolean shouldAddAttribute = false;

        if (resultAttributes != null && !resultAttributes.isEmpty()) {
            if (resultAttributes.contains(attributeName)) {
                shouldAddAttribute = true;
            } else {
                if (attributeName.lastIndexOf(':') != -1) {
                    String nonScopeAttr = attributeName.substring(attributeName.lastIndexOf(':') + 1);
                    if (resultAttributes.contains(nonScopeAttr)) {
                        shouldAddAttribute = true;
                    }
                }

                if (!shouldAddAttribute) {
                    int lastDot = attributeName.lastIndexOf(".");
                    if (lastDot != -1) {
                        String simpleAttrName = attributeName.substring(lastDot + 1);
                        if (resultAttributes.contains(simpleAttrName)) {
                            shouldAddAttribute = true;
                        }
                    }
                }
            }
        } else {
            shouldAddAttribute = true;
        }

        if (!shouldAddAttribute) {
            LOGGER.trace("Attribute is not supported in destination data model: {}", attributeName);
        }

        return shouldAddAttribute;
    }

    private static String buildAttr(String parentAttr, String attribute) {
        return parentAttr + "." + attribute;
    }

    private static Map<String, List<String>> getAttrMap(List<String> attributes) {
        return attributes.stream().map(ATTRIBUTE_PATTERN::matcher).filter(Matcher::matches).collect(
                Collectors.groupingBy(m -> m.group(2), Collectors.mapping(m -> m.group(3), Collectors.toList())));
    }

    private static boolean processEntry(String entryName, List<String> requiredAttrs, List<String> parsedAttrs) {
        final ThreadLocal<Boolean> dataIsValid = new ThreadLocal<>();
        dataIsValid.set(true);

        if (requiredAttrs != null) {
            requiredAttrs.stream().filter(requiredAttr -> !parsedAttrs.contains(requiredAttr))
                    .forEach(missingAttr -> {
                        dataIsValid.set(false);
                        LOGGER.warn("Node: {} is missing attribute: {}", entryName, missingAttr);
                    });
        }

        return dataIsValid.get();
    }

    public static String getMetacardId(Metacard metacard) {
        String id = metacard.getId();

        Attribute origIdAttr = metacard.getAttribute(MetacardVersion.VERSION_OF_ID);
        if (origIdAttr != null && origIdAttr.getValue().toString() != null) {
            id = origIdAttr.getValue().toString();
        }

        return id;
    }
}