aboutsummaryrefslogtreecommitdiffstats
path: root/src/main/java/org/onap/aai/datagrooming/DataGrooming.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/org/onap/aai/datagrooming/DataGrooming.java')
-rw-r--r--src/main/java/org/onap/aai/datagrooming/DataGrooming.java2853
1 files changed, 2853 insertions, 0 deletions
diff --git a/src/main/java/org/onap/aai/datagrooming/DataGrooming.java b/src/main/java/org/onap/aai/datagrooming/DataGrooming.java
new file mode 100644
index 0000000..6149dd9
--- /dev/null
+++ b/src/main/java/org/onap/aai/datagrooming/DataGrooming.java
@@ -0,0 +1,2853 @@
+/**
+ * ============LICENSE_START=======================================================
+ * org.onap.aai
+ * ================================================================================
+ * Copyright © 2017-2018 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+package org.onap.aai.datagrooming;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Properties;
+import java.util.Set;
+import java.util.UUID;
+
+import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal;
+import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource;
+import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__;
+import org.apache.tinkerpop.gremlin.structure.Direction;
+import org.apache.tinkerpop.gremlin.structure.Edge;
+import org.apache.tinkerpop.gremlin.structure.Graph;
+import org.apache.tinkerpop.gremlin.structure.Property;
+import org.apache.tinkerpop.gremlin.structure.Vertex;
+import org.apache.tinkerpop.gremlin.structure.VertexProperty;
+import org.onap.aai.GraphAdminApp;
+import org.onap.aai.dbmap.AAIGraph;
+import org.onap.aai.dbmap.AAIGraphConfig;
+import org.onap.aai.exceptions.AAIException;
+import org.onap.aai.introspection.Introspector;
+import org.onap.aai.introspection.Loader;
+import org.onap.aai.introspection.LoaderFactory;
+import org.onap.aai.introspection.ModelType;
+import org.onap.aai.introspection.exceptions.AAIUnknownObjectException;
+import org.onap.aai.logging.ErrorLogHelper;
+import org.onap.aai.logging.LogFormatTools;
+import org.onap.aai.logging.LoggingContext;
+import org.onap.aai.edges.enums.AAIDirection;
+import org.onap.aai.edges.enums.EdgeProperty;
+import org.onap.aai.setup.SchemaVersions;
+import org.onap.aai.util.*;
+import org.onap.aai.logging.LoggingContext.StatusCode;
+
+import com.att.eelf.configuration.Configuration;
+import com.att.eelf.configuration.EELFLogger;
+import com.att.eelf.configuration.EELFManager;
+import org.janusgraph.core.JanusGraphFactory;
+import org.janusgraph.core.JanusGraph;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+
+
+public class DataGrooming {
+
+ private static EELFLogger LOGGER = EELFManager.getInstance().getLogger(DataGrooming.class);
+
+ private static final String FROMAPPID = "AAI-DB";
+ private static final String TRANSID = UUID.randomUUID().toString();
+ private int dupeGrpsDeleted = 0;
+
+ private LoaderFactory loaderFactory;
+ private SchemaVersions schemaVersions;
+
+ public DataGrooming(LoaderFactory loaderFactory, SchemaVersions schemaVersions){
+ this.loaderFactory = loaderFactory;
+ this.schemaVersions = schemaVersions;
+ }
+
+ public void execute(String[] args){
+
+ String ver = "version"; // Placeholder
+ Boolean doAutoFix = false;
+ Boolean edgesOnlyFlag = false;
+ Boolean dontFixOrphansFlag = false;
+ Boolean skipHostCheck = false;
+ Boolean singleCommits = false;
+ Boolean dupeCheckOff = false;
+ Boolean dupeFixOn = false;
+ Boolean ghost2CheckOff = false;
+ Boolean ghost2FixOn = false;
+ Boolean neverUseCache = false;
+ Boolean skipEdgeCheckFlag = false;
+ Boolean skipIndexUpdateFix = false;
+
+ // A value of 0 means that we will not have a time-window -- we will look
+ // at all nodes of the passed-in nodeType.
+ int timeWindowMinutes = 0;
+
+ int maxRecordsToFix = AAIConstants.AAI_GROOMING_DEFAULT_MAX_FIX;
+ int sleepMinutes = AAIConstants.AAI_GROOMING_DEFAULT_SLEEP_MINUTES;
+ try {
+ String maxFixStr = AAIConfig.get("aai.grooming.default.max.fix");
+ if( maxFixStr != null && !maxFixStr.equals("") ){
+ maxRecordsToFix = Integer.parseInt(maxFixStr);
+ }
+ String sleepStr = AAIConfig.get("aai.grooming.default.sleep.minutes");
+ if( sleepStr != null && !sleepStr.equals("") ){
+ sleepMinutes = Integer.parseInt(sleepStr);
+ }
+ }
+ catch ( Exception e ){
+ // Don't worry, we'll just use the defaults that we got from AAIConstants
+ LOGGER.warn("WARNING - could not pick up aai.grooming values from aaiconfig.properties file. ");
+ }
+
+ String prevFileName = "";
+ String singleNodeType = "";
+ dupeGrpsDeleted = 0;
+ FormatDate fd = new FormatDate("yyyyMMddHHmm", "GMT");
+ String dteStr = fd.getDateTime();
+
+ if (args.length > 0) {
+ // They passed some arguments in that will affect processing
+ for (int i = 0; i < args.length; i++) {
+ String thisArg = args[i];
+ if (thisArg.equals("-edgesOnly")) {
+ edgesOnlyFlag = true;
+ } else if (thisArg.equals("-autoFix")) {
+ doAutoFix = true;
+ } else if (thisArg.equals("-skipHostCheck")) {
+ skipHostCheck = true;
+ } else if (thisArg.equals("-dontFixOrphans")) {
+ dontFixOrphansFlag = true;
+ } else if (thisArg.equals("-singleCommits")) {
+ singleCommits = true;
+ } else if (thisArg.equals("-dupeCheckOff")) {
+ dupeCheckOff = true;
+ } else if (thisArg.equals("-dupeFixOn")) {
+ dupeFixOn = true;
+ } else if (thisArg.equals("-ghost2CheckOff")) {
+ ghost2CheckOff = true;
+ } else if (thisArg.equals("-neverUseCache")) {
+ neverUseCache = true;
+ } else if (thisArg.equals("-ghost2FixOn")) {
+ ghost2FixOn = true;
+ } else if (thisArg.equals("-skipEdgeChecks")) {
+ skipEdgeCheckFlag = true;
+ } else if (thisArg.equals("-skipIndexUpdateFix")) {
+ skipIndexUpdateFix = true;
+ } else if (thisArg.equals("-maxFix")) {
+ i++;
+ if (i >= args.length) {
+ LoggingContext.statusCode(StatusCode.ERROR);
+ LoggingContext.responseCode(LoggingContext.BUSINESS_PROCESS_ERROR);
+ LOGGER.error(" No value passed with -maxFix option. ");
+ AAISystemExitUtil.systemExitCloseAAIGraph(0);
+ }
+ String nextArg = args[i];
+ try {
+ maxRecordsToFix = Integer.parseInt(nextArg);
+ } catch (Exception e) {
+ LoggingContext.statusCode(StatusCode.ERROR);
+ LoggingContext.responseCode(LoggingContext.BUSINESS_PROCESS_ERROR);
+ LOGGER.error("Bad value passed with -maxFix option: ["
+ + nextArg + "]");
+ AAISystemExitUtil.systemExitCloseAAIGraph(0);
+ }
+ } else if (thisArg.equals("-sleepMinutes")) {
+ i++;
+ if (i >= args.length) {
+ LoggingContext.statusCode(StatusCode.ERROR);
+ LoggingContext.responseCode(LoggingContext.BUSINESS_PROCESS_ERROR);
+ LOGGER.error("No value passed with -sleepMinutes option.");
+ AAISystemExitUtil.systemExitCloseAAIGraph(0);
+ }
+ String nextArg = args[i];
+ try {
+ sleepMinutes = Integer.parseInt(nextArg);
+ } catch (Exception e) {
+ LoggingContext.statusCode(StatusCode.ERROR);
+ LoggingContext.responseCode(LoggingContext.BUSINESS_PROCESS_ERROR);
+ LOGGER.error("Bad value passed with -sleepMinutes option: ["
+ + nextArg + "]");
+ AAISystemExitUtil.systemExitCloseAAIGraph(0);
+ }
+ } else if (thisArg.equals("-timeWindowMinutes")) {
+ i++;
+ if (i >= args.length) {
+ LoggingContext.statusCode(StatusCode.ERROR);
+ LoggingContext.responseCode(LoggingContext.BUSINESS_PROCESS_ERROR);
+ LOGGER.error("No value passed with -timeWindowMinutes option.");
+ AAISystemExitUtil.systemExitCloseAAIGraph(0);
+ }
+ String nextArg = args[i];
+ try {
+ timeWindowMinutes = Integer.parseInt(nextArg);
+ } catch (Exception e) {
+ LoggingContext.statusCode(StatusCode.ERROR);
+ LoggingContext.responseCode(LoggingContext.BUSINESS_PROCESS_ERROR);
+ LOGGER.error("Bad value passed with -timeWindowMinutes option: ["
+ + nextArg + "]");
+ AAISystemExitUtil.systemExitCloseAAIGraph(0);
+ }
+
+ } else if (thisArg.equals("-f")) {
+ i++;
+ if (i >= args.length) {
+ LoggingContext.statusCode(StatusCode.ERROR);
+ LoggingContext.responseCode(LoggingContext.BUSINESS_PROCESS_ERROR);
+ LOGGER.error(" No value passed with -f option. ");
+ AAISystemExitUtil.systemExitCloseAAIGraph(0);
+ }
+ prevFileName = args[i];
+ } else if (thisArg.equals("-singleNodeType")) {
+ i++;
+ if (i >= args.length) {
+ LoggingContext.statusCode(StatusCode.ERROR);
+ LoggingContext.responseCode(LoggingContext.BUSINESS_PROCESS_ERROR);
+ LOGGER.error(" No value passed with -onlyThisNodeType option. ");
+ AAISystemExitUtil.systemExitCloseAAIGraph(0);
+ }
+ singleNodeType = args[i];
+ } else {
+ LoggingContext.statusCode(StatusCode.ERROR);
+ LoggingContext.responseCode(LoggingContext.BUSINESS_PROCESS_ERROR);
+ LOGGER.error(" Unrecognized argument passed to DataGrooming: ["
+ + thisArg + "]. ");
+ LOGGER.error(" Valid values are: -f -autoFix -maxFix -edgesOnly -skipEdgeChecks -dupeFixOn -donFixOrphans -timeWindowMinutes -sleepMinutes -neverUseCache");
+ AAISystemExitUtil.systemExitCloseAAIGraph(0);
+ }
+ }
+ }
+
+ String windowTag = "FULL";
+ if( timeWindowMinutes > 0 ){
+ windowTag = "PARTIAL";
+ }
+ String groomOutFileName = "dataGrooming." + windowTag + "." + dteStr + ".out";
+
+ try {
+ loaderFactory.createLoaderForVersion(ModelType.MOXY, schemaVersions.getDefaultVersion());
+ }
+ catch (Exception ex){
+ LoggingContext.statusCode(StatusCode.ERROR);
+ LoggingContext.responseCode(LoggingContext.BUSINESS_PROCESS_ERROR);
+ LOGGER.error("ERROR - Could not create loader " + LogFormatTools.getStackTop(ex));
+ AAISystemExitUtil.systemExitCloseAAIGraph(1);
+ }
+
+ try {
+ if (!prevFileName.equals("")) {
+ // They are trying to fix some data based on a data in a
+ // previous file.
+ LOGGER.info(" Call doTheGrooming() with a previous fileName ["
+ + prevFileName + "] for cleanup. ");
+ Boolean finalShutdownFlag = true;
+ Boolean cacheDbOkFlag = false;
+ doTheGrooming(prevFileName, edgesOnlyFlag, dontFixOrphansFlag,
+ maxRecordsToFix, groomOutFileName, ver, singleCommits,
+ dupeCheckOff, dupeFixOn, ghost2CheckOff, ghost2FixOn,
+ finalShutdownFlag, cacheDbOkFlag,
+ skipEdgeCheckFlag, timeWindowMinutes,
+ singleNodeType, skipIndexUpdateFix );
+ } else if (doAutoFix) {
+ // They want us to run the processing twice -- first to look for
+ // delete candidates, then after
+ // napping for a while, run it again and delete any candidates
+ // that were found by the first run.
+ // Note: we will produce a separate output file for each of the
+ // two runs.
+ LOGGER.info(" Doing an auto-fix call to Grooming. ");
+ LOGGER.info(" First, Call doTheGrooming() to look at what's out there. ");
+ Boolean finalShutdownFlag = false;
+ Boolean cacheDbOkFlag = true;
+ int fixCandCount = doTheGrooming("", edgesOnlyFlag,
+ dontFixOrphansFlag, maxRecordsToFix, groomOutFileName,
+ ver, singleCommits, dupeCheckOff, dupeFixOn, ghost2CheckOff, ghost2FixOn,
+ finalShutdownFlag, cacheDbOkFlag,
+ skipEdgeCheckFlag, timeWindowMinutes,
+ singleNodeType, skipIndexUpdateFix );
+ if (fixCandCount == 0) {
+ LOGGER.info(" No fix-Candidates were found by the first pass, so no second/fix-pass is needed. ");
+ } else {
+ // We'll sleep a little and then run a fix-pass based on the
+ // first-run's output file.
+ try {
+ LOGGER.info("About to sleep for " + sleepMinutes
+ + " minutes.");
+ int sleepMsec = sleepMinutes * 60 * 1000;
+ Thread.sleep(sleepMsec);
+ } catch (InterruptedException ie) {
+ LOGGER.info("\n >>> Sleep Thread has been Interrupted <<< ");
+ AAISystemExitUtil.systemExitCloseAAIGraph(0);
+ }
+
+ dteStr = fd.getDateTime();
+ String secondGroomOutFileName = "dataGrooming." + windowTag + "." + dteStr + ".out";
+ LOGGER.info(" Now, call doTheGrooming() a second time and pass in the name of the file "
+ + "generated by the first pass for fixing: ["
+ + groomOutFileName + "]");
+ finalShutdownFlag = true;
+ cacheDbOkFlag = false;
+ doTheGrooming(groomOutFileName, edgesOnlyFlag,
+ dontFixOrphansFlag, maxRecordsToFix,
+ secondGroomOutFileName, ver, singleCommits,
+ dupeCheckOff, dupeFixOn, ghost2CheckOff, ghost2FixOn,
+ finalShutdownFlag, cacheDbOkFlag,
+ skipEdgeCheckFlag, timeWindowMinutes,
+ singleNodeType, skipIndexUpdateFix );
+ }
+ } else {
+ // Do the grooming - plain vanilla (no fix-it-file, no
+ // auto-fixing)
+ Boolean finalShutdownFlag = true;
+ LOGGER.info(" Call doTheGrooming() ");
+ Boolean cacheDbOkFlag = true;
+ if( neverUseCache ){
+ // They have forbidden us from using a cached db connection.
+ cacheDbOkFlag = false;
+ }
+ doTheGrooming("", edgesOnlyFlag, dontFixOrphansFlag,
+ maxRecordsToFix, groomOutFileName, ver, singleCommits,
+ dupeCheckOff, dupeFixOn, ghost2CheckOff, ghost2FixOn,
+ finalShutdownFlag, cacheDbOkFlag,
+ skipEdgeCheckFlag, timeWindowMinutes,
+ singleNodeType, skipIndexUpdateFix );
+ }
+ } catch (Exception ex) {
+ LoggingContext.statusCode(StatusCode.ERROR);
+ LoggingContext.responseCode(LoggingContext.DATA_ERROR);
+ LOGGER.error("Exception while grooming data " + LogFormatTools.getStackTop(ex));
+ }
+ LOGGER.info(" Done! ");
+ AAISystemExitUtil.systemExitCloseAAIGraph(0);
+ }
+
+ /**
+ * The main method.
+ *
+ * @param args the arguments
+ */
+ public static void main(String[] args) {
+
+ // Set the logging file properties to be used by EELFManager
+ System.setProperty("aai.service.name", DataGrooming.class.getSimpleName());
+
+ LoggingContext.init();
+ LoggingContext.partnerName(FROMAPPID);
+ LoggingContext.serviceName(GraphAdminApp.APP_NAME);
+ LoggingContext.component("dataGrooming");
+ LoggingContext.targetEntity(GraphAdminApp.APP_NAME);
+ LoggingContext.targetServiceName("main");
+ LoggingContext.requestId(TRANSID);
+ LoggingContext.statusCode(StatusCode.COMPLETE);
+ LoggingContext.responseCode(LoggingContext.SUCCESS);
+
+ Properties props = System.getProperties();
+ props.setProperty(Configuration.PROPERTY_LOGGING_FILE_NAME, AAIConstants.AAI_LOGBACK_PROPS);
+ props.setProperty(Configuration.PROPERTY_LOGGING_FILE_PATH, AAIConstants.AAI_HOME_BUNDLECONFIG);
+
+ AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(
+ "org.onap.aai.config",
+ "org.onap.aai.setup"
+ );
+
+ LoaderFactory loaderFactory = ctx.getBean(LoaderFactory.class);
+ SchemaVersions schemaVersions = ctx.getBean(SchemaVersions.class);
+ DataGrooming dataGrooming = new DataGrooming(loaderFactory, schemaVersions);
+ dataGrooming.execute(args);
+ }
+
+ /**
+ * Do the grooming.
+ *
+ * @param fileNameForFixing the file name for fixing
+ * @param edgesOnlyFlag the edges only flag
+ * @param dontFixOrphansFlag the dont fix orphans flag
+ * @param maxRecordsToFix the max records to fix
+ * @param groomOutFileName the groom out file name
+ * @param version the version
+ * @param singleCommits the single commits
+ * @param dupeCheckOff the dupe check off
+ * @param dupeFixOn the dupe fix on
+ * @param ghost2CheckOff the ghost 2 check off
+ * @param ghost2FixOn the ghost 2 fix on
+ * @param finalShutdownFlag the final shutdown flag
+ * @param cacheDbOkFlag the cacheDbOk flag
+ * @return the int
+ */
+ private int doTheGrooming( String fileNameForFixing,
+ Boolean edgesOnlyFlag, Boolean dontFixOrphansFlag,
+ int maxRecordsToFix, String groomOutFileName, String version,
+ Boolean singleCommits,
+ Boolean dupeCheckOff, Boolean dupeFixOn,
+ Boolean ghost2CheckOff, Boolean ghost2FixOn,
+ Boolean finalShutdownFlag, Boolean cacheDbOkFlag,
+ Boolean skipEdgeCheckFlag, int timeWindowMinutes,
+ String singleNodeType, Boolean skipIndexUpdateFix ) {
+
+ LOGGER.debug(" Entering doTheGrooming \n");
+
+ int cleanupCandidateCount = 0;
+ long windowStartTime = 0; // Translation of the window into a starting timestamp
+ BufferedWriter bw = null;
+ JanusGraph graph = null;
+ JanusGraph graph2 = null;
+ int deleteCount = 0;
+ int dummyUpdCount = 0;
+ boolean executeFinalCommit = false;
+ Set<String> deleteCandidateList = new LinkedHashSet<>();
+ Set<String> processedVertices = new LinkedHashSet<>();
+ Set<String> postCommitRemoveList = new LinkedHashSet<>();
+
+ Graph g = null;
+ Graph g2 = null;
+ try {
+ if( timeWindowMinutes > 0 ){
+ // Translate the window value (ie. 30 minutes) into a unix timestamp like
+ // we use in the db - so we can select data created after that time.
+ windowStartTime = figureWindowStartTime( timeWindowMinutes );
+ }
+
+ AAIConfig.init();
+ String targetDir = AAIConstants.AAI_HOME + AAIConstants.AAI_FILESEP
+ + "logs" + AAIConstants.AAI_FILESEP + "data"
+ + AAIConstants.AAI_FILESEP + "dataGrooming";
+
+ // Make sure the target directory exists
+ new File(targetDir).mkdirs();
+
+ if (!fileNameForFixing.equals("")) {
+ deleteCandidateList = getDeleteList(targetDir,
+ fileNameForFixing, edgesOnlyFlag, dontFixOrphansFlag,
+ dupeFixOn);
+ }
+
+ if (deleteCandidateList.size() > maxRecordsToFix) {
+ LoggingContext.statusCode(StatusCode.ERROR);
+ LoggingContext.responseCode(LoggingContext.DATA_ERROR);
+ LOGGER.warn(" >> WARNING >> Delete candidate list size ("
+ + deleteCandidateList.size()
+ + ") is too big. The maxFix we are using is: "
+ + maxRecordsToFix
+ + ". No candidates will be deleted. ");
+ // Clear out the list so it won't be processed below.
+ deleteCandidateList = new LinkedHashSet<>();
+ }
+
+ String fullOutputFileName = targetDir + AAIConstants.AAI_FILESEP
+ + groomOutFileName;
+ File groomOutFile = new File(fullOutputFileName);
+ try {
+ groomOutFile.createNewFile();
+ } catch (IOException e) {
+ String emsg = " Problem creating output file ["
+ + fullOutputFileName + "], exception=" + e.getMessage();
+ throw new AAIException("AAI_6124", emsg);
+ }
+
+ LOGGER.info(" Will write to " + fullOutputFileName );
+ bw = new BufferedWriter(new FileWriter(groomOutFile.getAbsoluteFile()));
+ ErrorLogHelper.loadProperties();
+
+ LOGGER.info(" ---- NOTE --- about to open graph (takes a little while)--------\n");
+
+ if( cacheDbOkFlag ){
+ // Since we're just reading (not deleting/fixing anything), we can use
+ // a cached connection to the DB
+
+ // -- note JanusGraphFactory has been leaving db connections open
+ //graph = JanusGraphFactory.open(new AAIGraphConfig.Builder(AAIConstants.CACHED_DB_CONFIG).forService(DataGrooming.class.getSimpleName()).withGraphType("cached").buildConfiguration());
+ graph = AAIGraph.getInstance().getGraph();
+ }
+ else {
+ // -- note JanusGraphFactory has been leaving db connections open
+ //graph = JanusGraphFactory.open(new AAIGraphConfig.Builder(AAIConstants.REALTIME_DB_CONFIG).forService(DataGrooming.class.getSimpleName()).withGraphType("realtime1").buildConfiguration());
+ graph = AAIGraph.getInstance().getGraph();
+ }
+ if (graph == null) {
+ String emsg = "null graph object in DataGrooming\n";
+ throw new AAIException("AAI_6101", emsg);
+ }
+
+ LOGGER.debug(" Got the graph object. ");
+
+ g = graph.newTransaction();
+ if (g == null) {
+ String emsg = "null graphTransaction object in DataGrooming\n";
+ throw new AAIException("AAI_6101", emsg);
+ }
+ GraphTraversalSource source1 = g.traversal();
+
+ ArrayList<String> errArr = new ArrayList<>();
+ int totalNodeCount = 0;
+ HashMap<String, String> misMatchedHash = new HashMap<String, String>();
+ HashMap<String, Vertex> orphanNodeHash = new HashMap<String, Vertex>();
+ HashMap<String, Vertex> missingAaiNtNodeHash = new HashMap<String, Vertex>();
+ HashMap<String, Edge> oneArmedEdgeHash = new HashMap<String, Edge>();
+ HashMap<String, String> emptyVertexHash = new HashMap<String, String>();
+ HashMap<String, Vertex> ghostNodeHash = new HashMap<String, Vertex>();
+ ArrayList<String> dupeGroups = new ArrayList<>();
+
+ Loader loader = loaderFactory.createLoaderForVersion(ModelType.MOXY, schemaVersions.getDefaultVersion());
+
+
+ // NOTE --- At one point, we tried explicitly searching for
+ // nodes that were missing their aai-node-type (which does
+ // happen sometimes), but the search takes too long and cannot
+ // be restricted to a date-range since these nodes usually do
+ // not have timestamps either. Instead, when we run across them
+ // as orphans, we will not treat them as orphans, but catagorize
+ // them as "missingAaiNodeType" - which we will treat more like
+ // ghost nodes - that is, delete them without asking permission.
+ //
+ // Note Also - It's a little surprising that we can run
+ // across these when looking for orphans since that search at
+ // least begins based on a given aai-node-type. But watching
+ // where they come up, they are getting discovered when a node
+ // is looking for its parent node. So, say, a “tenant” node
+ // follows a “contains” edge and finds the bad node.
+
+
+
+ Set<Entry<String, Introspector>> entrySet = loader.getAllObjects().entrySet();
+ String ntList = "";
+ LOGGER.info(" Starting DataGrooming Processing ");
+
+ if (edgesOnlyFlag) {
+ LOGGER.info(" NOTE >> Skipping Node processing as requested. Will only process Edges. << ");
+ }
+ else {
+ for (Entry<String, Introspector> entry : entrySet) {
+ String nType = entry.getKey();
+ int thisNtCount = 0;
+ int thisNtDeleteCount = 0;
+
+ if( !singleNodeType.equals("") && !singleNodeType.equals(nType) ){
+ // We are only going to process this one node type
+ continue;
+ }
+
+ LOGGER.debug(" > Look at : [" + nType + "] ...");
+ ntList = ntList + "," + nType;
+
+ // Get a collection of the names of the key properties for this nodeType to use later
+ // Determine what the key fields are for this nodeType - use an arrayList so they
+ // can be gotten out in a consistent order.
+ Set <String> keyPropsSet = entry.getValue().getKeys();
+ ArrayList <String> keyProps = new ArrayList <String> ();
+ keyProps.addAll(keyPropsSet);
+
+ Set <String> indexedPropsSet = entry.getValue().getIndexedProperties();
+ ArrayList <String> indexedProps = new ArrayList <String> ();
+ indexedProps.addAll(indexedPropsSet);
+
+ Iterator<String> indPropItr = indexedProps.iterator();
+ HashMap <String,String> propTypeHash = new HashMap <String, String> ();
+ while( indPropItr.hasNext() ){
+ String propName = indPropItr.next();
+ String propType = entry.getValue().getType(propName);
+ propTypeHash.put(propName, propType);
+ }
+
+ // Get the types of nodes that this nodetype depends on for uniqueness (if any)
+ Collection <String> depNodeTypes = loader.introspectorFromName(nType).getDependentOn();
+
+ // Loop through all the nodes of this Node type
+ int lastShownForNt = 0;
+ ArrayList <Vertex> tmpList = new ArrayList <> ();
+ Iterator <Vertex> iterv = source1.V().has("aai-node-type",nType);
+ while (iterv.hasNext()) {
+ // We put the nodes into an ArrayList because the graph.query iterator can time out
+ tmpList.add(iterv.next());
+ }
+
+ Iterator <Vertex> iter = tmpList.iterator();
+ while (iter.hasNext()) {
+ try {
+ thisNtCount++;
+ if( thisNtCount == lastShownForNt + 1000 ){
+ lastShownForNt = thisNtCount;
+ LOGGER.debug("count for " + nType + " so far = " + thisNtCount );
+ }
+ Vertex thisVtx = iter.next();
+ if( windowStartTime > 0 ){
+ // They are using the time-window, so we only want nodes that are updated after a
+ // passed-in timestamp OR that have no last-modified-timestamp which means they are suspicious.
+ Object objModTimeStamp = thisVtx.property("aai-last-mod-ts").orElse(null);
+ if( objModTimeStamp != null ){
+ long thisNodeModTime = (long)objModTimeStamp;
+ if( thisNodeModTime < windowStartTime ){
+ // It has a last modified ts and is NOT in our window, so we can pass over it
+ continue;
+ }
+ }
+ }
+
+ String thisVid = thisVtx.id().toString();
+ if (processedVertices.contains(thisVid)) {
+ LOGGER.debug("skipping already processed vertex: " + thisVid);
+ continue;
+ }
+ totalNodeCount++;
+ List <Vertex> secondGetList = new ArrayList <> ();
+ // -----------------------------------------------------------------------
+ // For each vertex of this nodeType, we want to:
+ // a) make sure that it can be retrieved using it's AAI defined key
+ // b) make sure that it is not a duplicate
+ // -----------------------------------------------------------------------
+
+ // For this instance of this nodeType, get the key properties
+ HashMap<String, Object> propHashWithKeys = new HashMap<>();
+ Iterator<String> keyPropI = keyProps.iterator();
+ while (keyPropI.hasNext()) {
+ String propName = keyPropI.next();
+ String propVal = "";
+ //delete an already deleted vertex
+ Object obj = thisVtx.<Object>property(propName).orElse(null);
+ if (obj != null) {
+ propVal = obj.toString();
+ }
+ propHashWithKeys.put(propName, propVal);
+ }
+ try {
+ // If this node is dependent on another for uniqueness, then do the query from that parent node
+ // Note - all of our nodes that are dependent on others for uniqueness are
+ // "children" of that node.
+ boolean depNodeOk = true;
+ if( depNodeTypes.isEmpty() ){
+ // This kind of node is not dependent on any other.
+ // Make sure we can get it back using it's key properties (that is the
+ // phantom checking) and that we only get one. Note - we also need
+ // to collect data for a second type of dupe-checking which is done later.
+ secondGetList = getNodeJustUsingKeyParams( TRANSID, FROMAPPID, source1, nType,
+ propHashWithKeys, version );
+ }
+ else {
+ // This kind of node is dependent on another for uniqueness.
+ // Start at it's parent (the parent/containing vertex) and make sure we can get it
+ // back using it's key properties and that we only get one.
+ Iterator <Vertex> vertI2 = source1.V(thisVtx).union(__.inE().has(EdgeProperty.CONTAINS.toString(), AAIDirection.OUT.toString()).outV(), __.outE().has(EdgeProperty.CONTAINS.toString(), AAIDirection.IN.toString()).inV());
+ Vertex parentVtx = null;
+ // First we need to try to find the parent/containing vertex.
+ int pCount = 0;
+ while( vertI2 != null && vertI2.hasNext() ){
+ parentVtx = vertI2.next();
+ pCount++;
+ }
+ if( pCount <= 0 ){
+ // It's Missing it's dependent/parent/containing node - it's an orphan
+ depNodeOk = false;
+ if (deleteCandidateList.contains(thisVid)) {
+ boolean okFlag = true;
+ boolean updateOnlyFlag = false;
+ try {
+ processedVertices.add(thisVtx.id().toString());
+ Object ob = thisVtx.<Object>property("aai-node-type").orElse(null);
+ if( ob == null && !skipIndexUpdateFix ){
+ updateIndexedProps(thisVtx, thisVid, nType, propTypeHash, indexedProps);
+ updateOnlyFlag = true;
+ dummyUpdCount++;
+ // Since we are updating this delete candidate, not deleting it, we
+ // want it to show up as a delete candidate for this run also.
+ missingAaiNtNodeHash.put(thisVid, thisVtx);
+ }
+ else {
+ // There was an aai-node-type parameter, so we'll do the remove
+ thisVtx.remove();
+ deleteCount++;
+ thisNtDeleteCount++;
+ }
+ } catch (Exception e) {
+ okFlag = false;
+ LoggingContext.statusCode(StatusCode.ERROR);
+ LoggingContext.responseCode(LoggingContext.DATA_ERROR);
+ LOGGER.error("ERROR trying to delete delete Candidate VID = " + thisVid + " " + LogFormatTools.getStackTop(e));
+ }
+ if (okFlag){
+ if( updateOnlyFlag ) {
+ LOGGER.info(" Updated Indexes for Delete Candidate VID = " + thisVid);
+ }
+ else {
+ LOGGER.info(" DELETED Delete Candidate VID = " + thisVid);
+ }
+ }
+ } else {
+ // NOTE - Only nodes that are missing their parent/containing node are ever considered "orphaned".
+ // That is, you could have a node with no edges... which sounds like an orphan, but not all
+ // nodes require edges. For example, you could have a newly created "image" node which does not have
+ // any edges connected to it (using it) yet.
+ Object ob = thisVtx.<Object>property("aai-node-type").orElse(null);
+ if( ob == null ){
+ // Group this with missing-node-type guys - which
+ // we will delete more readily than orphans.
+ LOGGER.info(" >> Encountered a missingAaiNodeType while looking for the parent of a [" + nType + "] node.");
+ missingAaiNtNodeHash.put(thisVid, thisVtx);
+ }
+ else {
+ Object ob2 = thisVtx.<Object>property("aai-uuid").orElse(null);
+ String auid = "";
+ if( ob2 != null ){
+ auid = ob2.toString();
+ }
+ String checkDummyUid = thisVid + "dummy";
+ if( auid.equals(checkDummyUid) ){
+ // Group this with missing-node-type guys.
+ LOGGER.info(" >> Encountered a missingAaiNodeType mid-fix-node while looking for the parent of a [" + nType + "] node.");
+ missingAaiNtNodeHash.put(thisVid, thisVtx);
+ }
+ else {
+ // It's a regular old orphan
+ orphanNodeHash.put(thisVid, thisVtx);
+ }
+ }
+ }
+ }
+ else if ( pCount > 1 ){
+ // Not sure how this could happen? Should we do something here?
+ depNodeOk = false;
+ }
+ else {
+ // We found the parent - so use it to do the second-look.
+ // NOTE --- We're just going to do the same check from the other direction - because
+ // there could be duplicates or the pointer going the other way could be broken
+ ArrayList <Vertex> tmpListSec = new ArrayList <> ();
+
+ tmpListSec = getConnectedChildrenOfOneType( source1, parentVtx, nType ) ;
+ Iterator<Vertex> vIter = tmpListSec.iterator();
+ while (vIter.hasNext()) {
+ Vertex tmpV = vIter.next();
+ if( vertexHasTheseKeys(tmpV, propHashWithKeys) ){
+ secondGetList.add(tmpV);
+ }
+ }
+ }
+ }// end of -- else this is a dependent node -- piece
+
+ if( depNodeOk && (secondGetList == null || secondGetList.size() == 0) ){
+ // We could not get the node back using it's own key info.
+ // So, it's a PHANTOM
+ if (deleteCandidateList.contains(thisVid)) {
+ boolean okFlag = true;
+ boolean updateOnlyFlag = false;
+ try {
+ Object ob = thisVtx.<Object>property("aai-node-type").orElse(null);
+ if( ob == null && !skipIndexUpdateFix ){
+ updateIndexedProps(thisVtx, thisVid, nType, propTypeHash, indexedProps);
+ dummyUpdCount++;
+ updateOnlyFlag = true;
+ // Since we are updating this delete candidate, not deleting it, we
+ // want it to show up as a delete candidate for this run also.
+ missingAaiNtNodeHash.put(thisVid, thisVtx);
+ }
+ else {
+ // There was an aai-node-type parameter, so we'll do the remove
+ thisVtx.remove();
+ deleteCount++;
+ thisNtDeleteCount++;
+ }
+ } catch (Exception e) {
+ okFlag = false;
+ LoggingContext.statusCode(StatusCode.ERROR);
+ LoggingContext.responseCode(LoggingContext.DATA_ERROR);
+ LOGGER.error("ERROR trying to delete phantom VID = " + thisVid + " " + LogFormatTools.getStackTop(e));
+ }
+ if (okFlag){
+ if( updateOnlyFlag ) {
+ LOGGER.info(" Updated Indexes for Delete Candidate VID = " + thisVid);
+ }
+ else {
+ LOGGER.info(" DELETED VID = " + thisVid);
+ }
+ }
+ } else {
+ ghostNodeHash.put(thisVid, thisVtx);
+ }
+ }
+ else if( (secondGetList.size() > 1) && depNodeOk && !dupeCheckOff ){
+ // Found some DUPLICATES - need to process them
+ LOGGER.info(" - now check Dupes for this guy - ");
+ List<String> tmpDupeGroups = checkAndProcessDupes(
+ TRANSID, FROMAPPID, g, source1, version,
+ nType, secondGetList, dupeFixOn,
+ deleteCandidateList, singleCommits, dupeGroups, loader);
+ Iterator<String> dIter = tmpDupeGroups.iterator();
+ while (dIter.hasNext()) {
+ // Add in any newly found dupes to our running list
+ String tmpGrp = dIter.next();
+ LOGGER.info("Found set of dupes: [" + tmpGrp + "]");
+ dupeGroups.add(tmpGrp);
+ }
+ }
+ }
+ catch (AAIException e1) {
+ LoggingContext.statusCode(StatusCode.ERROR);
+ LoggingContext.responseCode(LoggingContext.DATA_ERROR);
+ LOGGER.warn(" For nodeType = " + nType + " Caught exception", e1);
+ errArr.add(e1.getErrorObject().toString());
+ }
+ catch (Exception e2) {
+ LoggingContext.statusCode(StatusCode.ERROR);
+ LoggingContext.responseCode(LoggingContext.DATA_ERROR);
+ LOGGER.warn(" For nodeType = " + nType
+ + " Caught exception", e2);
+ errArr.add(e2.getMessage());
+ }
+ }// try block to enclose looping over each single vertex
+ catch (Exception exx) {
+ LoggingContext.statusCode(StatusCode.ERROR);
+ LoggingContext.responseCode(LoggingContext.DATA_ERROR);
+ LOGGER.warn("WARNING from inside the while-verts-loop ", exx);
+ }
+
+ } // while loop for each record of a nodeType
+
+ if( depNodeTypes.isEmpty() && !dupeCheckOff ){
+ // For this nodeType, we haven't looked at the possibility of a
+ // non-dependent node where two verts have same key info
+ ArrayList<ArrayList<Vertex>> nonDependentDupeSets = new ArrayList<ArrayList<Vertex>>();
+ nonDependentDupeSets = getDupeSets4NonDepNodes(
+ TRANSID, FROMAPPID, g,
+ version, nType, tmpList,
+ keyProps, loader );
+ // For each set found (each set is for a unique instance of key-values),
+ // process the dupes found
+ Iterator<ArrayList<Vertex>> dsItr = nonDependentDupeSets.iterator();
+ while( dsItr.hasNext() ){
+ ArrayList<Vertex> dupeList = dsItr.next();
+ LOGGER.info(" - now check Dupes for some non-dependent guys - ");
+ List<String> tmpDupeGroups = checkAndProcessDupes(
+ TRANSID, FROMAPPID, g, source1, version,
+ nType, dupeList, dupeFixOn,
+ deleteCandidateList, singleCommits, dupeGroups, loader);
+ Iterator<String> dIter = tmpDupeGroups.iterator();
+ while (dIter.hasNext()) {
+ // Add in any newly found dupes to our running list
+ String tmpGrp = dIter.next();
+ LOGGER.info("Found set of dupes: [" + tmpGrp + "]");
+ dupeGroups.add(tmpGrp);
+ }
+ }
+
+ }// end of extra dupe check for non-dependent nodes
+
+ if ( (thisNtDeleteCount > 0) && singleCommits ) {
+ // NOTE - the singleCommits option is not used in normal processing
+ g.tx().commit();
+ g = AAIGraph.getInstance().getGraph().newTransaction();
+
+ }
+ thisNtDeleteCount = 0;
+ LOGGER.info( " Processed " + thisNtCount + " records for [" + nType + "], " + totalNodeCount + " total (in window) overall. " );
+
+ }// While-loop for each node type
+
+ }// end of check to make sure we weren't only supposed to do edges
+
+
+ if( !skipEdgeCheckFlag ){
+ // --------------------------------------------------------------------------------------
+ // Now, we're going to look for one-armed-edges. Ie. an edge that
+ // should have
+ // been deleted (because a vertex on one side was deleted) but
+ // somehow was not deleted.
+ // So the one end of it points to a vertexId -- but that vertex is
+ // empty.
+ // --------------------------------------------------------------------------------------
+
+ // To do some strange checking - we need a second graph object
+ LOGGER.debug(" ---- DEBUG --- about to open a SECOND graph (takes a little while)--------\n");
+ // Note - graph2 just reads - but we want it to use a fresh connection to
+ // the database, so we are NOT using the CACHED DB CONFIG here.
+
+ // -- note JanusGraphFactory has been leaving db connections open
+ //graph2 = JanusGraphFactory.open(new AAIGraphConfig.Builder(AAIConstants.REALTIME_DB_CONFIG).forService(DataGrooming.class.getSimpleName()).withGraphType("realtime2").buildConfiguration());
+ graph2 = AAIGraph.getInstance().getGraph();
+ if (graph2 == null) {
+ String emsg = "null graph2 object in DataGrooming\n";
+ throw new AAIException("AAI_6101", emsg);
+ } else {
+ LOGGER.debug("Got the graph2 object... \n");
+ }
+ g2 = graph2.newTransaction();
+ if (g2 == null) {
+ String emsg = "null graphTransaction2 object in DataGrooming\n";
+ throw new AAIException("AAI_6101", emsg);
+ }
+
+ ArrayList<Vertex> vertList = new ArrayList<>();
+ Iterator<Vertex> vItor3 = g.traversal().V();
+ // Gotta hold these in a List - or else HBase times out as you cycle
+ // through these
+ while (vItor3.hasNext()) {
+ Vertex v = vItor3.next();
+ vertList.add(v);
+ }
+ int counter = 0;
+ int lastShown = 0;
+ Iterator<Vertex> vItor2 = vertList.iterator();
+ LOGGER.info(" Checking for bad edges --- ");
+
+ while (vItor2.hasNext()) {
+ Vertex v = null;
+ try {
+ try {
+ v = vItor2.next();
+ } catch (Exception vex) {
+ LoggingContext.statusCode(StatusCode.ERROR);
+ LoggingContext.responseCode(LoggingContext.DATA_ERROR);
+ LOGGER.warn(">>> WARNING trying to get next vertex on the vItor2 ");
+ continue;
+ }
+
+ counter++;
+ String thisVertId = "";
+ try {
+ thisVertId = v.id().toString();
+ } catch (Exception ev) {
+ LoggingContext.statusCode(StatusCode.ERROR);
+ LoggingContext.responseCode(LoggingContext.DATA_ERROR);
+ LOGGER.warn("WARNING when doing getId() on a vertex from our vertex list. ");
+ continue;
+ }
+ if (ghostNodeHash.containsKey(thisVertId)) {
+ // This is a phantom node, so don't try to use it
+ LOGGER.info(" >> Skipping edge check for edges from vertexId = "
+ + thisVertId
+ + ", since that guy is a Phantom Node");
+ continue;
+ }
+
+ if( windowStartTime > 0 ){
+ // They are using the time-window, so we only want nodes that are updated after a
+ // passed-in timestamp OR that have no last-modified-timestamp which means they are suspicious.
+ Object objModTimeStamp = v.property("aai-last-mod-ts").orElse(null);
+ if( objModTimeStamp != null ){
+ long thisNodeModTime = (long)objModTimeStamp;
+ if( thisNodeModTime < windowStartTime ){
+ // It has a last modified ts and is NOT in our window, so we can pass over it
+ continue;
+ }
+ }
+ }
+
+ if (counter == lastShown + 250) {
+ lastShown = counter;
+ LOGGER.info("... Checking edges for vertex # "
+ + counter);
+ }
+ Iterator<Edge> eItor = v.edges(Direction.BOTH);
+ while (eItor.hasNext()) {
+ Edge e = null;
+ Vertex vIn = null;
+ Vertex vOut = null;
+ try {
+ e = eItor.next();
+ } catch (Exception iex) {
+ LoggingContext.statusCode(StatusCode.ERROR);
+ LoggingContext.responseCode(LoggingContext.DATA_ERROR);
+ LOGGER.warn(">>> WARNING trying to get next edge on the eItor ", iex);
+ continue;
+ }
+
+ try {
+ vIn = e.inVertex();
+ } catch (Exception err) {
+ LoggingContext.statusCode(StatusCode.ERROR);
+ LoggingContext.responseCode(LoggingContext.DATA_ERROR);
+ LOGGER.warn(">>> WARNING trying to get edge's In-vertex ", err);
+ }
+ String vNtI = "";
+ String vIdI = "";
+ Vertex ghost2 = null;
+
+ Boolean keysMissing = true;
+ Boolean cantGetUsingVid = false;
+ if (vIn != null) {
+ try {
+ Object ob = vIn.<Object>property("aai-node-type").orElse(null);
+ if (ob != null) {
+ vNtI = ob.toString();
+ keysMissing = anyKeyFieldsMissing(vNtI, vIn, loader);
+ }
+ ob = vIn.id();
+ long vIdLong = 0L;
+ if (ob != null) {
+ vIdI = ob.toString();
+ vIdLong = Long.parseLong(vIdI);
+ }
+
+ if( ! ghost2CheckOff ){
+ Vertex connectedVert = g2.traversal().V(vIdLong).next();
+ if( connectedVert == null ) {
+ LoggingContext.statusCode(StatusCode.ERROR);
+ LoggingContext.responseCode(LoggingContext.DATA_ERROR);
+ LOGGER.warn( "GHOST2 -- got NULL when doing getVertex for vid = " + vIdLong);
+ cantGetUsingVid = true;
+
+ // If we can NOT get this ghost with the SECOND graph-object,
+ // it is still a ghost since even though we can get data about it using the FIRST graph
+ // object.
+ try {
+ ghost2 = g.traversal().V(vIdLong).next();
+ }
+ catch( Exception ex){
+ LoggingContext.statusCode(StatusCode.ERROR);
+ LoggingContext.responseCode(LoggingContext.DATA_ERROR);
+ LOGGER.warn( "GHOST2 -- Could not get the ghost info for a bad edge for vtxId = " + vIdLong, ex);
+ }
+ if( ghost2 != null ){
+ ghostNodeHash.put(vIdI, ghost2);
+ }
+ }
+ }// end of the ghost2 checking
+ }
+ catch (Exception err) {
+ LoggingContext.statusCode(StatusCode.ERROR);
+ LoggingContext.responseCode(LoggingContext.DATA_ERROR);
+ LOGGER.warn(">>> WARNING trying to get edge's In-vertex props ", err);
+ }
+ }
+ if (keysMissing || vIn == null || vNtI.equals("")
+ || cantGetUsingVid) {
+ // this is a bad edge because it points to a vertex
+ // that isn't there anymore or is corrupted
+ String thisEid = e.id().toString();
+ if (deleteCandidateList.contains(thisEid) || deleteCandidateList.contains(vIdI)) {
+ boolean okFlag = true;
+ if (!vIdI.equals("")) {
+ // try to get rid of the corrupted vertex
+ try {
+ if( (ghost2 != null) && ghost2FixOn ){
+ ghost2.remove();
+ }
+ else {
+ vIn.remove();
+ }
+ if (singleCommits) {
+ // NOTE - the singleCommits option is not used in normal processing
+ g.tx().commit();
+ g = AAIGraph.getInstance().getGraph().newTransaction();
+ }
+ else {
+ executeFinalCommit = true;
+ }
+ deleteCount++;
+ } catch (Exception e1) {
+ okFlag = false;
+ LoggingContext.statusCode(StatusCode.ERROR);
+ LoggingContext.responseCode(LoggingContext.DATA_ERROR);
+ LOGGER.warn("WARNING when trying to delete bad-edge-connected VERTEX VID = "
+ + vIdI, e1);
+ }
+ if (okFlag) {
+ LOGGER.info(" DELETED vertex from bad edge = "
+ + vIdI);
+ }
+ } else {
+ // remove the edge if we couldn't get the
+ // vertex
+ try {
+ e.remove();
+ if (singleCommits) {
+ // NOTE - the singleCommits option is not used in normal processing
+ g.tx().commit();
+ g = AAIGraph.getInstance().getGraph().newTransaction();
+ }
+ else {
+ executeFinalCommit = true;
+ }
+ deleteCount++;
+ } catch (Exception ex) {
+ // NOTE - often, the exception is just
+ // that this edge has already been
+ // removed
+ okFlag = false;
+ LoggingContext.statusCode(StatusCode.ERROR);
+ LoggingContext.responseCode(LoggingContext.DATA_ERROR);
+ LOGGER.warn("WARNING when trying to delete edge = "
+ + thisEid);
+ }
+ if (okFlag) {
+ LOGGER.info(" DELETED edge = " + thisEid);
+ }
+ }
+ } else {
+ oneArmedEdgeHash.put(thisEid, e);
+ if ((vIn != null) && (vIn.id() != null)) {
+ emptyVertexHash.put(thisEid, vIn.id()
+ .toString());
+ }
+ }
+ }
+
+ try {
+ vOut = e.outVertex();
+ } catch (Exception err) {
+ LoggingContext.statusCode(StatusCode.ERROR);
+ LoggingContext.responseCode(LoggingContext.DATA_ERROR);
+ LOGGER.warn(">>> WARNING trying to get edge's Out-vertex ");
+ }
+ String vNtO = "";
+ String vIdO = "";
+ ghost2 = null;
+ keysMissing = true;
+ cantGetUsingVid = false;
+ if (vOut != null) {
+ try {
+ Object ob = vOut.<Object>property("aai-node-type").orElse(null);
+ if (ob != null) {
+ vNtO = ob.toString();
+ keysMissing = anyKeyFieldsMissing(vNtO,
+ vOut, loader);
+ }
+ ob = vOut.id();
+ long vIdLong = 0L;
+ if (ob != null) {
+ vIdO = ob.toString();
+ vIdLong = Long.parseLong(vIdO);
+ }
+
+ if( ! ghost2CheckOff ){
+ Vertex connectedVert = g2.traversal().V(vIdLong).next();
+ if( connectedVert == null ) {
+ cantGetUsingVid = true;
+ LOGGER.info( "GHOST2 -- got NULL when doing getVertex for vid = " + vIdLong);
+ // If we can get this ghost with the other graph-object, then get it -- it's still a ghost
+ try {
+ ghost2 = g.traversal().V(vIdLong).next();
+ }
+ catch( Exception ex){
+ LoggingContext.statusCode(StatusCode.ERROR);
+ LoggingContext.responseCode(LoggingContext.DATA_ERROR);
+ LOGGER.warn( "GHOST2 -- Could not get the ghost info for a bad edge for vtxId = " + vIdLong, ex);
+ }
+ if( ghost2 != null ){
+ ghostNodeHash.put(vIdO, ghost2);
+ }
+ }
+ }
+ } catch (Exception err) {
+ LoggingContext.statusCode(StatusCode.ERROR);
+ LoggingContext.responseCode(LoggingContext.DATA_ERROR);
+ LOGGER.warn(">>> WARNING trying to get edge's Out-vertex props ", err);
+ }
+ }
+ if (keysMissing || vOut == null || vNtO.equals("")
+ || cantGetUsingVid) {
+ // this is a bad edge because it points to a vertex
+ // that isn't there anymore
+ String thisEid = e.id().toString();
+ if (deleteCandidateList.contains(thisEid) || deleteCandidateList.contains(vIdO)) {
+ boolean okFlag = true;
+ if (!vIdO.equals("")) {
+ // try to get rid of the corrupted vertex
+ try {
+ if( (ghost2 != null) && ghost2FixOn ){
+ ghost2.remove();
+ }
+ else if (vOut != null) {
+ vOut.remove();
+ }
+ if (singleCommits) {
+ // NOTE - the singleCommits option is not used in normal processing
+ g.tx().commit();
+ g = AAIGraph.getInstance().getGraph().newTransaction();
+ }
+ else {
+ executeFinalCommit = true;
+ }
+ deleteCount++;
+ } catch (Exception e1) {
+ okFlag = false;
+ LoggingContext.statusCode(StatusCode.ERROR);
+ LoggingContext.responseCode(LoggingContext.DATA_ERROR);
+ LOGGER.warn("WARNING when trying to delete bad-edge-connected VID = "
+ + vIdO, e1);
+ }
+ if (okFlag) {
+ LOGGER.info(" DELETED vertex from bad edge = "
+ + vIdO);
+ }
+ } else {
+ // remove the edge if we couldn't get the
+ // vertex
+ try {
+ e.remove();
+ if (singleCommits) {
+ // NOTE - the singleCommits option is not used in normal processing
+ g.tx().commit();
+ g = AAIGraph.getInstance().getGraph().newTransaction();
+ }
+ else {
+ executeFinalCommit = true;
+ }
+ deleteCount++;
+ } catch (Exception ex) {
+ // NOTE - often, the exception is just
+ // that this edge has already been
+ // removed
+ okFlag = false;
+ LoggingContext.statusCode(StatusCode.ERROR);
+ LoggingContext.responseCode(LoggingContext.DATA_ERROR);
+ LOGGER.warn("WARNING when trying to delete edge = "
+ + thisEid, ex);
+ }
+ if (okFlag) {
+ LOGGER.info(" DELETED edge = " + thisEid);
+ }
+ }
+ } else {
+ oneArmedEdgeHash.put(thisEid, e);
+ if ((vOut != null) && (vOut.id() != null)) {
+ emptyVertexHash.put(thisEid, vOut.id()
+ .toString());
+ }
+ }
+ }
+ }// End of while-edges-loop
+ } catch (Exception exx) {
+ LoggingContext.statusCode(StatusCode.ERROR);
+ LoggingContext.responseCode(LoggingContext.DATA_ERROR);
+ LOGGER.warn("WARNING from in the while-verts-loop ", exx);
+ }
+ }// End of while-vertices-loop (the edge-checking)
+ LOGGER.info(" Done checking for bad edges --- ");
+ } // end of -- if we're not skipping the edge-checking
+
+
+ deleteCount = deleteCount + dupeGrpsDeleted;
+ if (!singleCommits && (deleteCount > 0 || dummyUpdCount > 0) ){
+ executeFinalCommit = true;
+ }
+
+ int ghostNodeCount = ghostNodeHash.size();
+ int orphanNodeCount = orphanNodeHash.size();
+ int oneArmedEdgeCount = oneArmedEdgeHash.size();
+ int missingAaiNtNodeCount = missingAaiNtNodeHash.size();
+ int dupeCount = dupeGroups.size();
+
+ deleteCount = deleteCount + dupeGrpsDeleted;
+
+ bw.write("\n\n ============ Summary ==============\n");
+ if( timeWindowMinutes == 0 ){
+ bw.write("Ran FULL data grooming (no time-window). \n");
+ }
+ else {
+ bw.write("Ran PARTIAL data grooming just looking at data added/updated in the last " + timeWindowMinutes + " minutes. \n");
+ }
+
+ bw.write("\nRan these nodeTypes: " + ntList + "\n\n");
+ bw.write("There were this many delete candidates from previous run = "
+ + deleteCandidateList.size() + "\n");
+ if (dontFixOrphansFlag) {
+ bw.write(" Note - we are not counting orphan nodes since the -dontFixOrphans parameter was used. \n");
+ }
+ bw.write("Deleted this many delete candidates = " + deleteCount
+ + "\n");
+ bw.write("Dummy-index-update to delete candidates = " + dummyUpdCount
+ + "\n");
+ bw.write("Total number of nodes looked at = " + totalNodeCount
+ + "\n");
+ bw.write("Ghost Nodes identified = " + ghostNodeCount + "\n");
+ bw.write("Orphan Nodes identified = " + orphanNodeCount + "\n");
+ bw.write("Missing aai-node-type Nodes identified = " + missingAaiNtNodeCount + "\n");
+ bw.write("Bad Edges identified = " + oneArmedEdgeCount + "\n");
+ bw.write("Duplicate Groups count = " + dupeCount + "\n");
+ bw.write("MisMatching Label/aai-node-type count = "
+ + misMatchedHash.size() + "\n");
+
+ bw.write("\n ------------- Delete Candidates ---------\n");
+ for (Map.Entry<String, Vertex> entry : ghostNodeHash
+ .entrySet()) {
+ String vid = entry.getKey();
+ bw.write("DeleteCandidate: Phantom Vid = [" + vid + "]\n");
+ cleanupCandidateCount++;
+ }
+ for (Map.Entry<String, Vertex> entry : missingAaiNtNodeHash
+ .entrySet()) {
+ String vid = entry.getKey();
+ bw.write("DeleteCandidate: Missing aai-node-type Vid = [" + vid + "]\n");
+ cleanupCandidateCount++;
+ }
+ for (Map.Entry<String, Vertex> entry : orphanNodeHash
+ .entrySet()) {
+ String vid = entry.getKey();
+ bw.write("DeleteCandidate: OrphanDepNode Vid = [" + vid + "]\n");
+ if (!dontFixOrphansFlag) {
+ cleanupCandidateCount++;
+ }
+ }
+ for (Map.Entry<String, Edge> entry : oneArmedEdgeHash.entrySet()) {
+ String eid = entry.getKey();
+ bw.write("DeleteCandidate: Bad EDGE Edge-id = [" + eid + "]\n");
+ cleanupCandidateCount++;
+ }
+
+ bw.write("\n-- NOTE - To see DeleteCandidates for Duplicates, you need to look in the Duplicates Detail section below.\n");
+
+ bw.write("\n ------------- GHOST NODES - detail ");
+ for (Map.Entry<String, Vertex> entry : ghostNodeHash
+ .entrySet()) {
+ try {
+ String vid = entry.getKey();
+ bw.write("\n ==> Phantom Vid = " + vid + "\n");
+ ArrayList<String> retArr = showPropertiesForNode(
+ TRANSID, FROMAPPID, entry.getValue());
+ for (String info : retArr) {
+ bw.write(info + "\n");
+ }
+ retArr = showAllEdgesForNode(TRANSID, FROMAPPID,
+ entry.getValue());
+ for (String info : retArr) {
+ bw.write(info + "\n");
+ }
+ } catch (Exception dex) {
+ LoggingContext.statusCode(StatusCode.ERROR);
+ LoggingContext.responseCode(LoggingContext.DATA_ERROR);
+ LOGGER.error("error trying to print detail info for a ghost-node: " + LogFormatTools.getStackTop(dex));
+ }
+ }
+
+ bw.write("\n ------------- Missing aai-node-type NODES - detail: ");
+ for (Map.Entry<String, Vertex> entry : missingAaiNtNodeHash
+ .entrySet()) {
+ try {
+ String vid = entry.getKey();
+ bw.write("\n> Missing aai-node-type Node Vid = " + vid + "\n");
+ ArrayList<String> retArr = showPropertiesForNode(
+ TRANSID, FROMAPPID, entry.getValue());
+ for (String info : retArr) {
+ bw.write(info + "\n");
+ }
+
+ retArr = showAllEdgesForNode(TRANSID, FROMAPPID,
+ entry.getValue());
+ for (String info : retArr) {
+ bw.write(info + "\n");
+ }
+ } catch (Exception dex) {
+ LoggingContext.statusCode(StatusCode.ERROR);
+ LoggingContext.responseCode(LoggingContext.DATA_ERROR);
+ LOGGER.error("error trying to print detail info for a node missing its aai-node-type " + LogFormatTools.getStackTop(dex));
+ }
+ }
+
+ bw.write("\n ------------- Missing Dependent Edge ORPHAN NODES - detail: ");
+ for (Map.Entry<String, Vertex> entry : orphanNodeHash
+ .entrySet()) {
+ try {
+ String vid = entry.getKey();
+ bw.write("\n> Orphan Node Vid = " + vid + "\n");
+ ArrayList<String> retArr = showPropertiesForNode(
+ TRANSID, FROMAPPID, entry.getValue());
+ for (String info : retArr) {
+ bw.write(info + "\n");
+ }
+
+ retArr = showAllEdgesForNode(TRANSID, FROMAPPID,
+ entry.getValue());
+ for (String info : retArr) {
+ bw.write(info + "\n");
+ }
+ } catch (Exception dex) {
+ LoggingContext.statusCode(StatusCode.ERROR);
+ LoggingContext.responseCode(LoggingContext.DATA_ERROR);
+ LOGGER.error("error trying to print detail info for a Orphan Node /missing dependent edge " + LogFormatTools.getStackTop(dex));
+ }
+ }
+
+ bw.write("\n ------------- EDGES pointing to empty/bad vertices: ");
+ for (Map.Entry<String, Edge> entry : oneArmedEdgeHash.entrySet()) {
+ try {
+ String eid = entry.getKey();
+ Edge thisE = entry.getValue();
+ String badVid = emptyVertexHash.get(eid);
+ bw.write("\n> Edge pointing to bad vertex (Vid = "
+ + badVid + ") EdgeId = " + eid + "\n");
+ bw.write("Label: [" + thisE.label() + "]\n");
+ Iterator<Property<Object>> pI = thisE.properties();
+ while (pI.hasNext()) {
+ Property<Object> propKey = pI.next();
+ bw.write("Prop: [" + propKey + "], val = ["
+ + propKey.value() + "]\n");
+ }
+ } catch (Exception pex) {
+ LoggingContext.statusCode(StatusCode.ERROR);
+ LoggingContext.responseCode(LoggingContext.DATA_ERROR);
+ LOGGER.error("error trying to print empty/bad vertex data: " + LogFormatTools.getStackTop(pex));
+ }
+ }
+
+ bw.write("\n ------------- Duplicates: ");
+ Iterator<String> dupeIter = dupeGroups.iterator();
+ int dupeSetCounter = 0;
+ while (dupeIter.hasNext()) {
+ dupeSetCounter++;
+ String dset = (String) dupeIter.next();
+
+ bw.write("\n --- Duplicate Group # " + dupeSetCounter
+ + " Detail -----------\n");
+ try {
+ // We expect each line to have at least two vid's, followed
+ // by the preferred one to KEEP
+ String[] dupeArr = dset.split("\\|");
+ ArrayList<String> idArr = new ArrayList<>();
+ int lastIndex = dupeArr.length - 1;
+ for (int i = 0; i <= lastIndex; i++) {
+ if (i < lastIndex) {
+ // This is not the last entry, it is one of the
+ // dupes, so we want to show all its info
+ bw.write(" >> Duplicate Group # "
+ + dupeSetCounter + " Node # " + i
+ + " ----\n");
+ String vidString = dupeArr[i];
+ idArr.add(vidString);
+ long longVertId = Long.parseLong(vidString);
+ Iterator<Vertex> vtxIterator = g.vertices(longVertId);
+ Vertex vtx = null;
+ if (vtxIterator.hasNext()) {
+ vtx = vtxIterator.next();
+ }
+ ArrayList<String> retArr = showPropertiesForNode(TRANSID, FROMAPPID, vtx);
+ for (String info : retArr) {
+ bw.write(info + "\n");
+ }
+
+ retArr = showAllEdgesForNode(TRANSID,
+ FROMAPPID, vtx);
+ for (String info : retArr) {
+ bw.write(info + "\n");
+ }
+ } else {
+ // This is the last entry which should tell us if we
+ // have a preferred keeper
+ String prefString = dupeArr[i];
+ if (prefString.equals("KeepVid=UNDETERMINED")) {
+ bw.write("\n For this group of duplicates, could not tell which one to keep.\n");
+ bw.write(" >>> This group needs to be taken care of with a manual/forced-delete.\n");
+ } else {
+ // If we know which to keep, then the prefString
+ // should look like, "KeepVid=12345"
+ String[] prefArr = prefString.split("=");
+ if (prefArr.length != 2
+ || (!prefArr[0].equals("KeepVid"))) {
+ throw new Exception("Bad format. Expecting KeepVid=999999");
+ } else {
+ String keepVidStr = prefArr[1];
+ if (idArr.contains(keepVidStr)) {
+ bw.write("\n The vertex we want to KEEP has vertexId = "
+ + keepVidStr);
+ bw.write("\n The others become delete candidates: \n");
+ idArr.remove(keepVidStr);
+ for (int x = 0; x < idArr.size(); x++) {
+ cleanupCandidateCount++;
+ bw.write("DeleteCandidate: Duplicate Vid = ["
+ + idArr.get(x) + "]\n");
+ }
+ } else {
+ throw new Exception("ERROR - Vertex Id to keep not found in list of dupes. dset = ["
+ + dset + "]");
+ }
+ }
+ }// else we know which one to keep
+ }// else last entry
+ }// for each vertex in a group
+ } catch (Exception dex) {
+ LoggingContext.statusCode(StatusCode.ERROR);
+ LoggingContext.responseCode(LoggingContext.DATA_ERROR);
+ LOGGER.error("error trying to print duplicate vertex data " + LogFormatTools.getStackTop(dex));
+ }
+
+ }// while - work on each group of dupes
+
+ bw.write("\n ------------- Mis-matched Label/aai-node-type Nodes: \n ");
+ for (Map.Entry<String, String> entry : misMatchedHash.entrySet()) {
+ String msg = entry.getValue();
+ bw.write("MixedMsg = " + msg + "\n");
+ }
+
+ bw.write("\n ------------- Got these errors while processing: \n");
+ Iterator<String> errIter = errArr.iterator();
+ while (errIter.hasNext()) {
+ String line = (String) errIter.next();
+ bw.write(line + "\n");
+ }
+
+ bw.close();
+
+ LOGGER.info("\n ------------- Done doing all the checks ------------ ");
+ LOGGER.info("Output will be written to " + fullOutputFileName);
+
+ if (cleanupCandidateCount > 0) {
+ // Technically, this is not an error -- but we're throwing this
+ // error so that hopefully a
+ // monitoring system will pick it up and do something with it.
+ throw new AAIException("AAI_6123", "See file: [" + fullOutputFileName
+ + "] and investigate delete candidates. ");
+ }
+ } catch (AAIException e) {
+ LoggingContext.statusCode(StatusCode.ERROR);
+ LoggingContext.responseCode(LoggingContext.DATA_ERROR);
+ LOGGER.error("Caught AAIException while grooming data");
+ ErrorLogHelper.logException(e);
+ } catch (Exception ex) {
+ LoggingContext.statusCode(StatusCode.ERROR);
+ LoggingContext.responseCode(LoggingContext.DATA_ERROR);
+ LOGGER.error("Caught exception while grooming data");
+ ErrorLogHelper.logError("AAI_6128", ex.getMessage() + ", resolve and rerun dataGrooming");
+ } finally {
+
+ if (bw != null) {
+ try {
+ bw.close();
+ } catch (IOException iox) {
+ LoggingContext.statusCode(StatusCode.ERROR);
+ LoggingContext.responseCode(LoggingContext.AVAILABILITY_TIMEOUT_ERROR);
+ LOGGER.warn("Got an IOException trying to close bufferedWriter() \n", iox);
+ }
+ }
+
+ if (executeFinalCommit) {
+ // If we were holding off on commits till the end - then now is the time.
+ if( g == null ){
+ LoggingContext.statusCode(StatusCode.ERROR);
+ LoggingContext.responseCode(LoggingContext.DATA_ERROR);
+ LOGGER.error(" >>>> ERROR <<<< Could not commit changes. graph was null when we wanted to commit.");
+ }
+ else if( !g.tx().isOpen() ){
+ LoggingContext.statusCode(StatusCode.ERROR);
+ LoggingContext.responseCode(LoggingContext.DATA_ERROR);
+ LOGGER.error(" >>>> ERROR <<<< Could not commit changes. Transaction was not open when we wanted to commit.");
+ }
+ else {
+ try {
+ LOGGER.info("About to do the commit for "
+ + deleteCount + " removes. ");
+ g.tx().commit();
+ LOGGER.info("Commit was successful ");
+ } catch (Exception excom) {
+ LoggingContext.statusCode(StatusCode.ERROR);
+ LoggingContext.responseCode(LoggingContext.DATA_ERROR);
+ LOGGER.error(" >>>> ERROR <<<< Could not commit changes. " + LogFormatTools.getStackTop(excom));
+ deleteCount = 0;
+ }
+ }
+ }
+ else if (g != null && g.tx().isOpen()) {
+ try {
+ // We did not do any deletes that need to be committed.
+ // The rollback is to clear out the transaction used while doing those reads
+ g.tx().rollback();
+ } catch (Exception ex) {
+ // Don't throw anything because JanusGraph sometimes is just saying that the graph is already closed
+ LoggingContext.statusCode(StatusCode.ERROR);
+ LoggingContext.responseCode(LoggingContext.AVAILABILITY_TIMEOUT_ERROR);
+ LOGGER.warn("WARNING from final graphTransaction.rollback()", ex);
+ }
+ }
+
+ if (g2 != null && g2.tx().isOpen()) {
+ try {
+ // We only read on g2. The rollback is to clear out the transaction used while doing those reads
+ g2.tx().rollback();
+ } catch (Exception ex) {
+ // Don't throw anything because JanusGraph sometimes is just saying that the graph is already closed
+ LoggingContext.statusCode(StatusCode.ERROR);
+ LoggingContext.responseCode(LoggingContext.AVAILABILITY_TIMEOUT_ERROR);
+ LOGGER.warn("WARNING from final graphTransaction2.rollback()", ex);
+ }
+ }
+
+ if( finalShutdownFlag ){
+ try {
+ if( graph != null && graph.isOpen() ){
+ graph.tx().close();
+ if( "true".equals(System.getProperty("org.onap.aai.graphadmin.started"))) {
+ // Since dataGrooming was called from a scheduled task - do not call graph.close()
+ }
+ else {
+ // DataGrooming must have been called manually - so we need to call close().
+ graph.close();
+ }
+ }
+ } catch (Exception ex) {
+ // Don't throw anything because JanusGraph sometimes is just saying that the graph is already closed{
+ LoggingContext.statusCode(StatusCode.ERROR);
+ LoggingContext.responseCode(LoggingContext.AVAILABILITY_TIMEOUT_ERROR);
+ LOGGER.warn("WARNING from final graph.shutdown()", ex);
+ }
+
+ try {
+ if( graph2 != null && graph2.isOpen() ){
+ graph2.tx().close();
+ if( "true".equals(System.getProperty("org.onap.aai.graphadmin.started"))) {
+ // Since dataGrooming was called from a scheduled task - do not call graph2.close()
+ }
+ else {
+ // DataGrooming must have been called manually - so we need to call close().
+ graph2.close();
+ }
+ }
+ } catch (Exception ex) {
+ // Don't throw anything because JanusGraph sometimes is just saying that the graph is already closed{
+ LoggingContext.statusCode(StatusCode.ERROR);
+ LoggingContext.responseCode(LoggingContext.AVAILABILITY_TIMEOUT_ERROR);
+ LOGGER.warn("WARNING from final graph2.shutdown()", ex);
+ }
+ }
+
+ }
+
+ return cleanupCandidateCount;
+
+ }// end of doTheGrooming()
+
+
+ private void updateIndexedProps(Vertex thisVtx, String thisVidStr, String nType,
+ HashMap <String,String>propTypeHash, ArrayList <String> indexedProps) {
+ // This is a "missing-aai-node-type" scenario.
+ // Other indexes may also be messed up, so we will update all of them on
+ // this pass. A future pass will just treat this node like a regular orphan
+ // and delete it (if appropriate).
+ LOGGER.info(" We will be updating the indexed properties for this node to dummy values. VID = " + thisVidStr );
+ String dummyPropValStr = thisVidStr + "dummy";
+ // These reserved-prop-names are all indexed for all nodes
+ thisVtx.property("aai-node-type",nType);
+ thisVtx.property("aai-uri", dummyPropValStr);
+ thisVtx.property("aai-unique-key", dummyPropValStr);
+ thisVtx.property("aai-uuid", dummyPropValStr);
+ thisVtx.property("source-of-truth", dummyPropValStr);
+ Iterator<String> indexedPropI = indexedProps.iterator();
+ while (indexedPropI.hasNext()) {
+ String propName = indexedPropI.next();
+ // Using the VID in case this property is unique in the db and
+ // we're doing this kind of thing on more than one of these nodes..
+ String dataType = propTypeHash.get(propName);
+ if( dataType == null || dataType.toLowerCase().endsWith(".string") ){
+ thisVtx.property(propName, dummyPropValStr);
+ }
+ else if( dataType.toLowerCase().endsWith(".long") ){
+ Long thisVidLong = (Long) thisVtx.id();
+ thisVtx.property(propName, thisVidLong);
+ }
+ else if( dataType.toLowerCase().endsWith(".boolean") ){
+ thisVtx.property(propName, false);
+ }
+ else if( dataType.toLowerCase().endsWith(".integer") ){
+ thisVtx.property(propName, 9999);
+ }
+ else {
+ // Not sure what it is - try a string
+ thisVtx.property(propName, dummyPropValStr);
+ }
+ }
+ }
+
+ /**
+ * Vertex has these keys.
+ *
+ * @param tmpV the tmp V
+ * @param propHashWithKeys the prop hash with keys
+ * @return the boolean
+ */
+ private Boolean vertexHasTheseKeys( Vertex tmpV, HashMap <String, Object> propHashWithKeys) {
+ Iterator <?> it = propHashWithKeys.entrySet().iterator();
+ while( it.hasNext() ){
+ String propName = "";
+ String propVal = "";
+ Map.Entry <?,?>propEntry = (Map.Entry<?,?>)it.next();
+ Object propNameObj = propEntry.getKey();
+ if( propNameObj != null ){
+ propName = propNameObj.toString();
+ }
+ Object propValObj = propEntry.getValue();
+ if( propValObj != null ){
+ propVal = propValObj.toString();
+ }
+ Object checkValObj = tmpV.<Object>property(propName).orElse(null);
+ if( checkValObj == null ) {
+ return false;
+ }
+ else if( !propVal.equals(checkValObj.toString()) ){
+ return false;
+ }
+ }
+ return true;
+ }
+
+
+ /**
+ * Any key fields missing.
+ *
+ * @param nType the n type
+ * @param v the v
+ * @return the boolean
+ */
+ private Boolean anyKeyFieldsMissing(String nType, Vertex v, Loader loader) {
+
+ try {
+ Introspector obj = null;
+ try {
+ obj = loader.introspectorFromName(nType);
+ } catch (AAIUnknownObjectException e) {
+ // They gave us a non-empty nodeType but our NodeKeyProps does
+ // not have data for it. Since we do not know what the
+ // key params are for this type of node, we will just
+ // return "false".
+ String emsg = " -- WARNING -- Unrecognized nodeType: [" + nType
+ + "]. We cannot determine required keys for this nType. ";
+ // NOTE - this will be caught below and a "false" returned
+ throw new AAIException("AAI_6121", emsg);
+ }
+
+ // Determine what the key fields are for this nodeType
+ Collection <String> keyPropNamesColl = obj.getKeys();
+ Iterator<String> keyPropI = keyPropNamesColl.iterator();
+ while (keyPropI.hasNext()) {
+ String propName = keyPropI.next();
+ Object ob = v.<Object>property(propName).orElse(null);
+ if (ob == null || ob.toString().equals("")) {
+ // It is missing a key property
+ return true;
+ }
+ }
+ } catch (AAIException e) {
+ // Something was wrong -- but since we weren't able to check
+ // the keys, we will not declare that it is missing keys.
+ return false;
+ }
+ return false;
+ }
+
+
+ /**
+ * Gets the delete list.
+ *
+ * @param targetDir the target dir
+ * @param fileName the file name
+ * @param edgesOnlyFlag the edges only flag
+ * @param dontFixOrphans the dont fix orphans
+ * @param dupeFixOn the dupe fix on
+ * @return the delete list
+ * @throws AAIException the AAI exception
+ */
+ private Set<String> getDeleteList(String targetDir,
+ String fileName, Boolean edgesOnlyFlag, Boolean dontFixOrphans,
+ Boolean dupeFixOn) throws AAIException {
+
+ // Look in the file for lines formated like we expect - pull out any
+ // Vertex Id's to delete on this run
+ Set<String> delList = new LinkedHashSet<>();
+ String fullFileName = targetDir + AAIConstants.AAI_FILESEP + fileName;
+
+ try(BufferedReader br = new BufferedReader(new FileReader(fullFileName))) {
+ String line = br.readLine();
+ while (line != null) {
+ if (!"".equals(line) && line.startsWith("DeleteCandidate")) {
+ if (edgesOnlyFlag && (!line.contains("Bad Edge"))) {
+ // We're only processing edges and this line is not for an edge
+ } else if (dontFixOrphans && line.contains("Orphan")) {
+ // We're not going to process orphans
+ } else if (!dupeFixOn && line.contains("Duplicate")) {
+ // We're not going to process Duplicates
+ } else {
+ int begIndex = line.indexOf("id = ");
+ int endIndex = line.indexOf("]");
+ String vidVal = line.substring(begIndex + 6, endIndex);
+ delList.add(vidVal);
+ }
+ }
+ line = br.readLine();
+ }
+ br.close();
+ } catch (IOException e) {
+ throw new AAIException("AAI_6124", e, "Could not open input-file [" + fullFileName
+ + "], exception= " + e.getMessage());
+ }
+
+ return delList;
+
+ }// end of getDeleteList
+
+ /**
+ * Gets the preferred dupe.
+ *
+ * @param transId the trans id
+ * @param fromAppId the from app id
+ * @param g the g
+ * @param dupeVertexList the dupe vertex list
+ * @param ver the ver
+ * @return Vertex
+ * @throws AAIException the AAI exception
+ */
+ public Vertex getPreferredDupe(String transId,
+ String fromAppId, GraphTraversalSource g,
+ ArrayList<Vertex> dupeVertexList, String ver, Loader loader)
+ throws AAIException {
+
+ // This method assumes that it is being passed a List of vertex objects
+ // which
+ // violate our uniqueness constraints.
+
+ Vertex nullVtx = null;
+
+ if (dupeVertexList == null) {
+ return nullVtx;
+ }
+ int listSize = dupeVertexList.size();
+ if (listSize == 0) {
+ return nullVtx;
+ }
+ if (listSize == 1) {
+ return (dupeVertexList.get(0));
+ }
+
+ Vertex vtxPreferred = null;
+ Vertex currentFaveVtx = dupeVertexList.get(0);
+ for (int i = 1; i < listSize; i++) {
+ Vertex vtxB = dupeVertexList.get(i);
+ vtxPreferred = pickOneOfTwoDupes(transId, fromAppId, g,
+ currentFaveVtx, vtxB, ver, loader);
+ if (vtxPreferred == null) {
+ // We couldn't choose one
+ return nullVtx;
+ } else {
+ currentFaveVtx = vtxPreferred;
+ }
+ }
+
+ return (currentFaveVtx);
+
+ } // end of getPreferredDupe()
+
+ /**
+ * Pick one of two dupes.
+ *
+ * @param transId the trans id
+ * @param fromAppId the from app id
+ * @param g the g
+ * @param vtxA the vtx A
+ * @param vtxB the vtx B
+ * @param ver the ver
+ * @return Vertex
+ * @throws AAIException the AAI exception
+ */
+ public Vertex pickOneOfTwoDupes(String transId,
+ String fromAppId, GraphTraversalSource g, Vertex vtxA,
+ Vertex vtxB, String ver, Loader loader) throws AAIException {
+
+ Vertex nullVtx = null;
+ Vertex preferredVtx = null;
+
+ Long vidA = new Long(vtxA.id().toString());
+ Long vidB = new Long(vtxB.id().toString());
+
+ String vtxANodeType = "";
+ String vtxBNodeType = "";
+ Object objType = vtxA.<Object>property("aai-node-type").orElse(null);
+ if (objType != null) {
+ vtxANodeType = objType.toString();
+ }
+ objType = vtxB.<Object>property("aai-node-type").orElse(null);
+ if (objType != null) {
+ vtxBNodeType = objType.toString();
+ }
+
+ if (vtxANodeType.equals("") || (!vtxANodeType.equals(vtxBNodeType))) {
+ // Either they're not really dupes or there's some bad data - so
+ // don't pick one
+ return nullVtx;
+ }
+
+ // Check that node A and B both have the same key values (or else they
+ // are not dupes)
+ // (We'll check dep-node later)
+ // Determine what the key fields are for this nodeType
+ Collection <String> keyProps = new ArrayList <>();
+ HashMap <String,Object> keyPropValsHash = new HashMap <String,Object>();
+ try {
+ keyProps = loader.introspectorFromName(vtxANodeType).getKeys();
+ } catch (AAIUnknownObjectException e) {
+ LOGGER.warn("Required property not found", e);
+ throw new AAIException("AAI_6105", "Required Property name(s) not found for nodeType = " + vtxANodeType + ")");
+ }
+
+ Iterator<String> keyPropI = keyProps.iterator();
+ while (keyPropI.hasNext()) {
+ String propName = keyPropI.next();
+ String vtxAKeyPropVal = "";
+ objType = vtxA.<Object>property(propName).orElse(null);
+ if (objType != null) {
+ vtxAKeyPropVal = objType.toString();
+ }
+ String vtxBKeyPropVal = "";
+ objType = vtxB.<Object>property(propName).orElse(null);
+ if (objType != null) {
+ vtxBKeyPropVal = objType.toString();
+ }
+
+ if (vtxAKeyPropVal.equals("")
+ || (!vtxAKeyPropVal.equals(vtxBKeyPropVal))) {
+ // Either they're not really dupes or they are missing some key
+ // data - so don't pick one
+ return nullVtx;
+ }
+ else {
+ // Keep these around for (potential) use later
+ keyPropValsHash.put(propName, vtxAKeyPropVal);
+ }
+
+ }
+
+ // Collect the vid's and aai-node-types of the vertices that each vertex
+ // (A and B) is connected to.
+ ArrayList<String> vtxIdsConn2A = new ArrayList<>();
+ ArrayList<String> vtxIdsConn2B = new ArrayList<>();
+ HashMap<String, String> nodeTypesConn2A = new HashMap<>();
+ HashMap<String, String> nodeTypesConn2B = new HashMap<>();
+
+ ArrayList<Vertex> vertListA = getConnectedNodes( g, vtxA );
+ if (vertListA != null) {
+ Iterator<Vertex> iter = vertListA.iterator();
+ while (iter.hasNext()) {
+ Vertex tvCon = iter.next();
+ String conVid = tvCon.id().toString();
+ String nt = "";
+ objType = tvCon.<Object>property("aai-node-type").orElse(null);
+ if (objType != null) {
+ nt = objType.toString();
+ }
+ nodeTypesConn2A.put(nt, conVid);
+ vtxIdsConn2A.add(conVid);
+ }
+ }
+
+ ArrayList<Vertex> vertListB = getConnectedNodes( g, vtxB );
+ if (vertListB != null) {
+ Iterator<Vertex> iter = vertListB.iterator();
+ while (iter.hasNext()) {
+ Vertex tvCon = iter.next();
+ String conVid = tvCon.id().toString();
+ String nt = "";
+ objType = tvCon.<Object>property("aai-node-type").orElse(null);
+ if (objType != null) {
+ nt = objType.toString();
+ }
+ nodeTypesConn2B.put(nt, conVid);
+ vtxIdsConn2B.add(conVid);
+ }
+ }
+
+ // 1 - If this kind of node needs a dependent node for uniqueness, then
+ // verify that they both nodes point to the same dependent
+ // node (otherwise they're not really duplicates)
+ // Note - there are sometimes more than one dependent node type since
+ // one nodeType can be used in different ways. But for a
+ // particular node, it will only have one dependent node that
+ // it's connected to.
+ String onlyNodeThatIndexPointsToVidStr = "";
+ Collection<String> depNodeTypes = loader.introspectorFromName(vtxANodeType).getDependentOn();
+ if (depNodeTypes.isEmpty()) {
+ // This kind of node is not dependent on any other. That is ok.
+ // We need to find out if the unique index info is good or not and
+ // use that later when deciding if we can delete one.
+ onlyNodeThatIndexPointsToVidStr = findJustOneUsingIndex( transId,
+ fromAppId, g, keyPropValsHash, vtxANodeType, vidA, vidB, ver );
+ } else {
+ String depNodeVtxId4A = "";
+ String depNodeVtxId4B = "";
+ Iterator<String> iter = depNodeTypes.iterator();
+ while (iter.hasNext()) {
+ String depNodeType = iter.next();
+ if (nodeTypesConn2A.containsKey(depNodeType)) {
+ // This is the dependent node type that vertex A is using
+ depNodeVtxId4A = nodeTypesConn2A.get(depNodeType);
+ }
+ if (nodeTypesConn2B.containsKey(depNodeType)) {
+ // This is the dependent node type that vertex B is using
+ depNodeVtxId4B = nodeTypesConn2B.get(depNodeType);
+ }
+ }
+ if (depNodeVtxId4A.equals("")
+ || (!depNodeVtxId4A.equals(depNodeVtxId4B))) {
+ // Either they're not really dupes or there's some bad data - so
+ // don't pick either one
+ return nullVtx;
+ }
+ }
+
+ if (vtxIdsConn2A.size() == vtxIdsConn2B.size()) {
+ // 2 - If they both have edges to all the same vertices,
+ // then return the one that can be reached uniquely via the
+ // key if that is the case or
+ // else the one with the lower vertexId
+
+ boolean allTheSame = true;
+ Iterator<String> iter = vtxIdsConn2A.iterator();
+ while (iter.hasNext()) {
+ String vtxIdConn2A = iter.next();
+ if (!vtxIdsConn2B.contains(vtxIdConn2A)) {
+ allTheSame = false;
+ break;
+ }
+ }
+
+ if (allTheSame) {
+ // If everything is the same, but one of the two has a good
+ // pointer to it, then save that one. Otherwise, take the
+ // older one.
+ if( !onlyNodeThatIndexPointsToVidStr.equals("") ){
+ // only one is reachable via the index - choose that one.
+ if( onlyNodeThatIndexPointsToVidStr.equals(vidA.toString()) ){
+ preferredVtx = vtxA;
+ }
+ else if( onlyNodeThatIndexPointsToVidStr.equals(vidB.toString()) ){
+ preferredVtx = vtxB;
+ }
+ }
+ else if (vidA < vidB) {
+ preferredVtx = vtxA;
+ } else {
+ preferredVtx = vtxB;
+ }
+ }
+ } else if (vtxIdsConn2A.size() > vtxIdsConn2B.size()) {
+ // 3 - VertexA is connected to more things than vtxB.
+ // We'll pick VtxA if its edges are a superset of vtxB's edges
+ // and it doesn't contradict the check for the index/key pointer.
+ boolean missingOne = false;
+ Iterator<String> iter = vtxIdsConn2B.iterator();
+ while (iter.hasNext()) {
+ String vtxIdConn2B = iter.next();
+ if (!vtxIdsConn2A.contains(vtxIdConn2B)) {
+ missingOne = true;
+ break;
+ }
+ }
+ if (!missingOne) {
+ if( onlyNodeThatIndexPointsToVidStr.equals("")
+ || onlyNodeThatIndexPointsToVidStr.equals(vidA.toString()) ){
+ preferredVtx = vtxA;
+ }
+ }
+ } else if (vtxIdsConn2B.size() > vtxIdsConn2A.size()) {
+ // 4 - VertexB is connected to more things than vtxA.
+ // We'll pick VtxB if its edges are a superset of vtxA's edges
+ // and it doesn't contradict the check for the index/key pointer.
+ boolean missingOne = false;
+ Iterator<String> iter = vtxIdsConn2A.iterator();
+ while (iter.hasNext()) {
+ String vtxIdConn2A = iter.next();
+ if (!vtxIdsConn2B.contains(vtxIdConn2A)) {
+ missingOne = true;
+ break;
+ }
+ }
+ if (!missingOne) {
+ if( onlyNodeThatIndexPointsToVidStr.equals("")
+ || onlyNodeThatIndexPointsToVidStr.equals(vidB.toString()) ){
+ preferredVtx = vtxB;
+ }
+ }
+ } else {
+ preferredVtx = nullVtx;
+ }
+
+ return (preferredVtx);
+
+ } // end of pickOneOfTwoDupes()
+
+ /**
+ * Check and process dupes.
+ *
+ * @param transId the trans id
+ * @param fromAppId the from app id
+ * @param g the g
+ * @param version the version
+ * @param nType the n type
+ * @param passedVertList the passed vert list
+ * @param dupeFixOn the dupe fix on
+ * @param deleteCandidateList the delete candidate list
+ * @param singleCommits the single commits
+ * @param alreadyFoundDupeGroups the already found dupe groups
+ * @return the array list
+ */
+ private List<String> checkAndProcessDupes(String transId,
+ String fromAppId, Graph g, GraphTraversalSource source, String version, String nType,
+ List<Vertex> passedVertList, Boolean dupeFixOn,
+ Set<String> deleteCandidateList, Boolean singleCommits,
+ ArrayList<String> alreadyFoundDupeGroups, Loader loader ) {
+
+ ArrayList<String> returnList = new ArrayList<>();
+ ArrayList<Vertex> checkVertList = new ArrayList<>();
+ ArrayList<String> alreadyFoundDupeVidArr = new ArrayList<>();
+ Boolean noFilterList = true;
+ Iterator<String> afItr = alreadyFoundDupeGroups.iterator();
+ while (afItr.hasNext()) {
+ String dupeGrpStr = afItr.next();
+ String[] dupeArr = dupeGrpStr.split("\\|");
+ int lastIndex = dupeArr.length - 1;
+ for (int i = 0; i < lastIndex; i++) {
+ // Note: we don't want the last one...
+ String vidString = dupeArr[i];
+ alreadyFoundDupeVidArr.add(vidString);
+ noFilterList = false;
+ }
+ }
+
+ // For a given set of Nodes that were found with a set of KEY
+ // Parameters, (nodeType + key data) we will
+ // see if we find any duplicate nodes that need to be cleaned up. Note -
+ // it's legit to have more than one
+ // node with the same key data if the nodes depend on a parent for
+ // uniqueness -- as long as the two nodes
+ // don't hang off the same Parent.
+ // If we find duplicates, and we can figure out which of each set of
+ // duplicates is the one that we
+ // think should be preserved, we will record that. Whether we can tell
+ // which one should be
+ // preserved or not, we will return info about any sets of duplicates
+ // found.
+ //
+ // Each element in the returned arrayList might look like this:
+ // "1234|5678|keepVid=UNDETERMINED" (if there were 2 dupes, and we
+ // couldn't figure out which one to keep)
+ // or, "100017|200027|30037|keepVid=30037" (if there were 3 dupes and we
+ // thought the third one was the one that should survive)
+
+ // Because of the way the calling code loops over stuff, we can get the
+ // same data multiple times - so we should
+ // not process any vertices that we've already seen.
+
+ try {
+ Iterator<Vertex> pItr = passedVertList.iterator();
+ while (pItr.hasNext()) {
+ Vertex tvx = pItr.next();
+ String passedId = tvx.id().toString();
+ if (noFilterList || !alreadyFoundDupeVidArr.contains(passedId)) {
+ // We haven't seen this one before - so we should check it.
+ checkVertList.add(tvx);
+ }
+ }
+
+ if (checkVertList.size() < 2) {
+ // Nothing new to check.
+ return returnList;
+ }
+
+ if (loader.introspectorFromName(nType).isTopLevel()) {
+ // If this was a node that does NOT depend on other nodes for
+ // uniqueness, and we
+ // found more than one node using its key -- record the found
+ // vertices as duplicates.
+ String dupesStr = "";
+ for (int i = 0; i < checkVertList.size(); i++) {
+ dupesStr = dupesStr
+ + ((checkVertList.get(i))).id()
+ .toString() + "|";
+ }
+ if (dupesStr != "") {
+ Vertex prefV = getPreferredDupe(transId, fromAppId,
+ source, checkVertList, version, loader);
+ if (prefV == null) {
+ // We could not determine which duplicate to keep
+ dupesStr = dupesStr + "KeepVid=UNDETERMINED";
+ returnList.add(dupesStr);
+ } else {
+ dupesStr = dupesStr + "KeepVid=" + prefV.id();
+ Boolean didRemove = false;
+ if (dupeFixOn) {
+ didRemove = deleteNonKeepersIfAppropriate(g,
+ dupesStr, prefV.id().toString(),
+ deleteCandidateList, singleCommits);
+ }
+ if (didRemove) {
+ dupeGrpsDeleted++;
+ } else {
+ // keep them on our list
+ returnList.add(dupesStr);
+ }
+ }
+ }
+ } else {
+ // More than one node have the same key fields since they may
+ // depend on a parent node for uniqueness. Since we're finding
+ // more than one, we want to check to see if any of the
+ // vertices that have this set of keys (and are the same nodeType)
+ // are also pointing at the same 'parent' node.
+ // Note: for a given set of key data, it is possible that there
+ // could be more than one set of duplicates.
+ HashMap<String, ArrayList<Vertex>> vertsGroupedByParentHash = groupVertsByDepNodes(
+ transId, fromAppId, source, version, nType,
+ checkVertList, loader);
+ for (Map.Entry<String, ArrayList<Vertex>> entry : vertsGroupedByParentHash
+ .entrySet()) {
+ ArrayList<Vertex> thisParentsVertList = entry
+ .getValue();
+ if (thisParentsVertList.size() > 1) {
+ // More than one vertex found with the same key info
+ // hanging off the same parent/dependent node
+ String dupesStr = "";
+ for (int i = 0; i < thisParentsVertList.size(); i++) {
+ dupesStr = dupesStr
+ + ((thisParentsVertList
+ .get(i))).id() + "|";
+ }
+ if (dupesStr != "") {
+ Vertex prefV = getPreferredDupe(transId,
+ fromAppId, source, thisParentsVertList,
+ version, loader);
+
+ if (prefV == null) {
+ // We could not determine which duplicate to
+ // keep
+ dupesStr = dupesStr + "KeepVid=UNDETERMINED";
+ returnList.add(dupesStr);
+ } else {
+ Boolean didRemove = false;
+ dupesStr = dupesStr + "KeepVid="
+ + prefV.id().toString();
+ if (dupeFixOn) {
+ didRemove = deleteNonKeepersIfAppropriate(
+ g, dupesStr, prefV.id()
+ .toString(),
+ deleteCandidateList, singleCommits);
+ }
+ if (didRemove) {
+ dupeGrpsDeleted++;
+ } else {
+ // keep them on our list
+ returnList.add(dupesStr);
+ }
+ }
+ }
+ }
+ }
+ }
+ } catch (Exception e) {
+ LoggingContext.statusCode(StatusCode.ERROR);
+ LoggingContext.responseCode(LoggingContext.DATA_ERROR);
+ LOGGER.warn(" >>> Threw an error in checkAndProcessDupes - just absorb this error and move on. ", e);
+ }
+
+ return returnList;
+
+ }// End of checkAndProcessDupes()
+
+ /**
+ * Group verts by dep nodes.
+ *
+ * @param transId the trans id
+ * @param fromAppId the from app id
+ * @param g the g
+ * @param version the version
+ * @param nType the n type
+ * @param passedVertList the passed vert list
+ * @return the hash map
+ * @throws AAIException the AAI exception
+ */
+ private HashMap<String, ArrayList<Vertex>> groupVertsByDepNodes(
+ String transId, String fromAppId, GraphTraversalSource g, String version,
+ String nType, ArrayList<Vertex> passedVertList, Loader loader)
+ throws AAIException {
+ // Given a list of JanusGraph Vertices of one nodeType (see AAI-8956), group
+ // them together by the parent node they depend on.
+ // Ie. if given a list of ip address nodes (assumed to all have the
+ // same key info) they might sit under several different parent vertices.
+ // Under Normal conditions, there would only be one per parent -- but
+ // we're trying to find duplicates - so we
+ // allow for the case where more than one is under the same parent node.
+
+ HashMap<String, ArrayList<Vertex>> retHash = new HashMap<String, ArrayList<Vertex>>();
+ if (loader.introspectorFromName(nType).isTopLevel()) {
+ // This method really should not have been called if this is not the
+ // kind of node
+ // that depends on a parent for uniqueness, so just return the empty
+ // hash.
+ return retHash;
+ }
+
+ // Find out what types of nodes the passed in nodes can depend on
+ ArrayList<String> depNodeTypeL = new ArrayList<>();
+ Collection<String> depNTColl = loader.introspectorFromName(nType).getDependentOn();
+ Iterator<String> ntItr = depNTColl.iterator();
+ while (ntItr.hasNext()) {
+ depNodeTypeL.add(ntItr.next());
+ }
+ // For each vertex, we want find its depended-on/parent vertex so we
+ // can track what other vertexes that are dependent on that same guy.
+ if (passedVertList != null) {
+ Iterator<Vertex> iter = passedVertList.iterator();
+ while (iter.hasNext()) {
+ Vertex thisVert = iter.next();
+ Vertex tmpParentVtx = getConnectedParent( g, thisVert );
+ if( tmpParentVtx != null ) {
+ String parentNt = null;
+ Object obj = tmpParentVtx.<Object>property("aai-node-type").orElse(null);
+ if (obj != null) {
+ parentNt = obj.toString();
+ }
+ if (depNTColl.contains(parentNt)) {
+ // This must be the parent/dependent node
+ String parentVid = tmpParentVtx.id().toString();
+ if (retHash.containsKey(parentVid)) {
+ // add this vert to the list for this parent key
+ retHash.get(parentVid).add(thisVert);
+ } else {
+ // This is the first one we found on this parent
+ ArrayList<Vertex> vList = new ArrayList<>();
+ vList.add(thisVert);
+ retHash.put(parentVid, vList);
+ }
+ }
+ }
+ }
+ }
+
+ return retHash;
+
+ }// end of groupVertsByDepNodes()
+
+ /**
+ * Delete non keepers if appropriate.
+ *
+ * @param g the g
+ * @param dupeInfoString the dupe info string
+ * @param vidToKeep the vid to keep
+ * @param deleteCandidateList the delete candidate list
+ * @param singleCommits the single commits
+ * @return the boolean
+ */
+ private Boolean deleteNonKeepersIfAppropriate(Graph g,
+ String dupeInfoString, String vidToKeep,
+ Set<String> deleteCandidateList, Boolean singleCommits) {
+
+ Boolean deletedSomething = false;
+ // This assumes that the dupeInfoString is in the format of
+ // pipe-delimited vid's followed by
+ // ie. "3456|9880|keepVid=3456"
+ if (deleteCandidateList == null || deleteCandidateList.size() == 0) {
+ // No vid's on the candidate list -- so no deleting will happen on
+ // this run
+ return false;
+ }
+
+ String[] dupeArr = dupeInfoString.split("\\|");
+ ArrayList<String> idArr = new ArrayList<>();
+ int lastIndex = dupeArr.length - 1;
+ for (int i = 0; i <= lastIndex; i++) {
+ if (i < lastIndex) {
+ // This is not the last entry, it is one of the dupes,
+ String vidString = dupeArr[i];
+ idArr.add(vidString);
+ } else {
+ // This is the last entry which should tell us if we have a
+ // preferred keeper
+ String prefString = dupeArr[i];
+ if (prefString.equals("KeepVid=UNDETERMINED")) {
+ // They sent us a bad string -- nothing should be deleted if
+ // no dupe could be tagged as preferred
+ return false;
+ } else {
+ // If we know which to keep, then the prefString should look
+ // like, "KeepVid=12345"
+ String[] prefArr = prefString.split("=");
+ if (prefArr.length != 2 || (!prefArr[0].equals("KeepVid"))) {
+ LoggingContext.statusCode(StatusCode.ERROR);
+ LoggingContext.responseCode(LoggingContext.DATA_ERROR);
+ LOGGER.error("Bad format. Expecting KeepVid=999999");
+ return false;
+ } else {
+ String keepVidStr = prefArr[1];
+ if (idArr.contains(keepVidStr)) {
+ idArr.remove(keepVidStr);
+
+ // So now, the idArr should just contain the vid's
+ // that we want to remove.
+ for (int x = 0; x < idArr.size(); x++) {
+ boolean okFlag = true;
+ String thisVid = idArr.get(x);
+ if (deleteCandidateList.contains(thisVid)) {
+ // This vid is a valid delete candidate from
+ // a prev. run, so we can remove it.
+ try {
+ long longVertId = Long
+ .parseLong(thisVid);
+ Vertex vtx = g
+ .traversal().V(longVertId).next();
+ vtx.remove();
+
+ if (singleCommits) {
+ // NOTE - the singleCommits option is not used in normal processing
+ g.tx().commit();
+ g = AAIGraph.getInstance().getGraph().newTransaction();
+ }
+ } catch (Exception e) {
+ okFlag = false;
+ LoggingContext.statusCode(StatusCode.ERROR);
+ LoggingContext.responseCode(LoggingContext.DATA_ERROR);
+ LOGGER.error("ERROR trying to delete VID = " + thisVid + " " + LogFormatTools.getStackTop(e));
+ }
+ if (okFlag) {
+ LOGGER.info(" DELETED VID = " + thisVid);
+ deletedSomething = true;
+ }
+ }
+ }
+ } else {
+ LoggingContext.statusCode(StatusCode.ERROR);
+ LoggingContext.responseCode(LoggingContext.DATA_ERROR);
+ LOGGER.error("ERROR - Vertex Id to keep not found in list of dupes. dupeInfoString = ["
+ + dupeInfoString + "]");
+ return false;
+ }
+ }
+ }// else we know which one to keep
+ }// else last entry
+ }// for each vertex in a group
+
+ return deletedSomething;
+
+ }// end of deleteNonKeepersIfAppropriate()
+
+
+ /**
+ * Gets the node just using key params.
+ *
+ * @param transId the trans id
+ * @param fromAppId the from app id
+ * @param graph the graph
+ * @param nodeType the node type
+ * @param keyPropsHash the key props hash
+ * @param apiVersion the api version
+ * @return the node just using key params
+ * @throws AAIException the AAI exception
+ */
+ public List <Vertex> getNodeJustUsingKeyParams( String transId, String fromAppId, GraphTraversalSource graph, String nodeType,
+ HashMap<String,Object> keyPropsHash, String apiVersion ) throws AAIException{
+
+ List <Vertex> retVertList = new ArrayList <> ();
+
+ // We assume that all NodeTypes have at least one key-property defined.
+ // Note - instead of key-properties (the primary key properties), a user could pass
+ // alternate-key values if they are defined for the nodeType.
+ List<String> kName = new ArrayList<>();
+ List<Object> kVal = new ArrayList<>();
+ if( keyPropsHash == null || keyPropsHash.isEmpty() ) {
+ throw new AAIException("AAI_6120", " NO key properties passed for this getNodeJustUsingKeyParams() request. NodeType = [" + nodeType + "]. ");
+ }
+
+ int i = -1;
+ for( Map.Entry<String, Object> entry : keyPropsHash.entrySet() ){
+ i++;
+ kName.add(i, entry.getKey());
+ kVal.add(i, entry.getValue());
+ }
+ int topPropIndex = i;
+ Vertex tiV = null;
+ String propsAndValuesForMsg = "";
+ Iterator <Vertex> verts = null;
+
+ try {
+ if( topPropIndex == 0 ){
+ propsAndValuesForMsg = " (" + kName.get(0) + " = " + kVal.get(0) + ") ";
+ verts= graph.V().has(kName.get(0),kVal.get(0)).has("aai-node-type",nodeType);
+ }
+ else if( topPropIndex == 1 ){
+ propsAndValuesForMsg = " (" + kName.get(0) + " = " + kVal.get(0) + ", "
+ + kName.get(1) + " = " + kVal.get(1) + ") ";
+ verts = graph.V().has(kName.get(0),kVal.get(0)).has(kName.get(1),kVal.get(1)).has("aai-node-type",nodeType);
+ }
+ else if( topPropIndex == 2 ){
+ propsAndValuesForMsg = " (" + kName.get(0) + " = " + kVal.get(0) + ", "
+ + kName.get(1) + " = " + kVal.get(1) + ", "
+ + kName.get(2) + " = " + kVal.get(2) + ") ";
+ verts= graph.V().has(kName.get(0),kVal.get(0)).has(kName.get(1),kVal.get(1)).has(kName.get(2),kVal.get(2)).has("aai-node-type",nodeType);
+ }
+ else if( topPropIndex == 3 ){
+ propsAndValuesForMsg = " (" + kName.get(0) + " = " + kVal.get(0) + ", "
+ + kName.get(1) + " = " + kVal.get(1) + ", "
+ + kName.get(2) + " = " + kVal.get(2) + ", "
+ + kName.get(3) + " = " + kVal.get(3) + ") ";
+ verts= graph.V().has(kName.get(0),kVal.get(0)).has(kName.get(1),kVal.get(1)).has(kName.get(2),kVal.get(2)).has(kName.get(3),kVal.get(3)).has("aai-node-type",nodeType);
+ }
+ else {
+ throw new AAIException("AAI_6114", " We only support 4 keys per nodeType for now \n");
+ }
+ }
+ catch( Exception ex ){
+ LoggingContext.statusCode(StatusCode.ERROR);
+ LoggingContext.responseCode(LoggingContext.DATA_ERROR);
+ LOGGER.error( " ERROR trying to get node for: [" + propsAndValuesForMsg + "]" + LogFormatTools.getStackTop(ex));
+ }
+
+ if( verts != null ){
+ while( verts.hasNext() ){
+ tiV = verts.next();
+ retVertList.add(tiV);
+ }
+ }
+
+ if( retVertList.size() == 0 ){
+ LOGGER.debug("DEBUG No node found for nodeType = [" + nodeType +
+ "], propsAndVal = " + propsAndValuesForMsg );
+ }
+
+ return retVertList;
+
+ }// End of getNodeJustUsingKeyParams()
+
+ /**
+ * Show all edges for node.
+ *
+ * @param transId the trans id
+ * @param fromAppId the from app id
+ * @param tVert the t vert
+ * @return the array list
+ */
+ private ArrayList <String> showAllEdgesForNode( String transId, String fromAppId, Vertex tVert ){
+
+ ArrayList <String> retArr = new ArrayList <> ();
+ Iterator <Edge> eI = tVert.edges(Direction.IN);
+ if( ! eI.hasNext() ){
+ retArr.add("No IN edges were found for this vertex. ");
+ }
+ while( eI.hasNext() ){
+ Edge ed = eI.next();
+ String lab = ed.label();
+ Vertex vtx;
+ if (tVert.equals(ed.inVertex())) {
+ vtx = ed.outVertex();
+ } else {
+ vtx = ed.inVertex();
+ }
+ if( vtx == null ){
+ retArr.add(" >>> COULD NOT FIND VERTEX on the other side of this edge edgeId = " + ed.id() + " <<< ");
+ }
+ else {
+ String nType = vtx.<String>property("aai-node-type").orElse(null);
+ String vid = vtx.id().toString();
+ retArr.add("Found an IN edge (" + lab + ") to this vertex from a [" + nType + "] node with VtxId = " + vid );
+
+ }
+ }
+
+ eI = tVert.edges(Direction.OUT);
+ if( ! eI.hasNext() ){
+ retArr.add("No OUT edges were found for this vertex. ");
+ }
+ while( eI.hasNext() ){
+ Edge ed = eI.next();
+ String lab = ed.label();
+ Vertex vtx;
+ if (tVert.equals(ed.inVertex())) {
+ vtx = ed.outVertex();
+ } else {
+ vtx = ed.inVertex();
+ }
+ if( vtx == null ){
+ retArr.add(" >>> COULD NOT FIND VERTEX on the other side of this edge edgeId = " + ed.id() + " <<< ");
+ }
+ else {
+ String nType = vtx.<String>property("aai-node-type").orElse(null);
+ String vid = vtx.id().toString();
+ retArr.add("Found an OUT edge (" + lab + ") from this vertex to a [" + nType + "] node with VtxId = " + vid );
+ }
+ }
+ return retArr;
+ }
+
+
+ /**
+ * Show properties for node.
+ *
+ * @param transId the trans id
+ * @param fromAppId the from app id
+ * @param tVert the t vert
+ * @return the array list
+ */
+ private ArrayList <String> showPropertiesForNode( String transId, String fromAppId, Vertex tVert ){
+
+ ArrayList <String> retArr = new ArrayList <> ();
+ if( tVert == null ){
+ retArr.add("null Node object passed to showPropertiesForNode()\n");
+ }
+ else {
+ String nodeType = "";
+ Object ob = tVert.<Object>property("aai-node-type").orElse(null);
+ if( ob == null ){
+ nodeType = "null";
+ }
+ else{
+ nodeType = ob.toString();
+ }
+
+ retArr.add(" AAINodeType/VtxID for this Node = [" + nodeType + "/" + tVert.id() + "]");
+ retArr.add(" Property Detail: ");
+ Iterator<VertexProperty<Object>> pI = tVert.properties();
+ while( pI.hasNext() ){
+ VertexProperty<Object> tp = pI.next();
+ Object val = tp.value();
+ retArr.add("Prop: [" + tp.key() + "], val = [" + val + "] ");
+ }
+ }
+ return retArr;
+ }
+
+
+ private ArrayList <Vertex> getConnectedNodes(GraphTraversalSource g, Vertex startVtx )
+ throws AAIException {
+
+ ArrayList <Vertex> retArr = new ArrayList <> ();
+ if( startVtx == null ){
+ return retArr;
+ }
+ else {
+ GraphTraversal<Vertex, Vertex> modPipe = null;
+ modPipe = g.V(startVtx).both();
+ if( modPipe != null && modPipe.hasNext() ){
+ while( modPipe.hasNext() ){
+ Vertex conVert = modPipe.next();
+ retArr.add(conVert);
+ }
+ }
+ }
+ return retArr;
+
+ }// End of getConnectedNodes()
+
+
+ private ArrayList <Vertex> getConnectedChildrenOfOneType( GraphTraversalSource g,
+ Vertex startVtx, String childNType ) throws AAIException{
+
+ ArrayList <Vertex> childList = new ArrayList <> ();
+ Iterator <Vertex> vertI = g.V(startVtx).union(__.outE().has(EdgeProperty.CONTAINS.toString(), AAIDirection.OUT.toString()).inV(), __.inE().has(EdgeProperty.CONTAINS.toString(), AAIDirection.IN.toString()).outV());
+
+ Vertex tmpVtx = null;
+ while( vertI != null && vertI.hasNext() ){
+ tmpVtx = vertI.next();
+ Object ob = tmpVtx.<Object>property("aai-node-type").orElse(null);
+ if (ob != null) {
+ String tmpNt = ob.toString();
+ if( tmpNt.equals(childNType)){
+ childList.add(tmpVtx);
+ }
+ }
+ }
+
+ return childList;
+
+ }// End of getConnectedChildrenOfOneType()
+
+
+ private Vertex getConnectedParent( GraphTraversalSource g,
+ Vertex startVtx ) throws AAIException{
+
+ Vertex parentVtx = null;
+ Iterator <Vertex> vertI = g.V(startVtx).union(__.inE().has(EdgeProperty.CONTAINS.toString(), AAIDirection.OUT.toString()).outV(), __.outE().has(EdgeProperty.CONTAINS.toString(), AAIDirection.IN.toString()).inV());
+
+ while( vertI != null && vertI.hasNext() ){
+ // Note - there better only be one!
+ parentVtx = vertI.next();
+ }
+
+ return parentVtx;
+
+ }// End of getConnectedParent()
+
+
+ private long figureWindowStartTime( int timeWindowMinutes ){
+ // Given a window size, calculate what the start-timestamp would be.
+
+ if( timeWindowMinutes <= 0 ){
+ // This just means that there is no window...
+ return 0;
+ }
+ long unixTimeNow = System.currentTimeMillis();
+ long windowInMillis = timeWindowMinutes * 60L * 1000;
+
+ long startTimeStamp = unixTimeNow - windowInMillis;
+
+ return startTimeStamp;
+ } // End of figureWindowStartTime()
+
+
+ /**
+ * Collect Duplicate Sets for nodes that are NOT dependent on parent nodes.
+ *
+ * @param transId the trans id
+ * @param fromAppId the from app id
+ * @param g the g
+ * @param version the version
+ * @param nType the n type
+ * @param passedVertList the passed vert list
+ * @return the array list
+ */
+ private ArrayList<ArrayList<Vertex>> getDupeSets4NonDepNodes( String transId,
+ String fromAppId, Graph g, String version, String nType,
+ ArrayList<Vertex> passedVertList,
+ ArrayList <String> keyPropNamesArr,
+ Loader loader ) {
+
+ ArrayList<ArrayList<Vertex>> returnList = new ArrayList<ArrayList<Vertex>>();
+
+ // We've been passed a set of nodes that we want to check.
+ // They are all NON-DEPENDENT nodes of the same nodeType meaning that they should be
+ // unique in the DB based on their KEY DATA alone. So, if
+ // we group them by their key data - if any key has more than one
+ // vertex mapped to it, those vertices are dupes.
+ //
+ // When we find duplicates, we group them in an ArrayList (there can be
+ // more than one duplicate for one set of key data)
+ // Then these dupeSets are grouped up and returned.
+ //
+
+ HashMap <String, ArrayList<String>> keyVals2VidHash = new HashMap <String, ArrayList<String>>();
+ HashMap <String,Vertex> vtxHash = new HashMap <String,Vertex>();
+ Iterator<Vertex> pItr = passedVertList.iterator();
+ while (pItr.hasNext()) {
+ try {
+ Vertex tvx = pItr.next();
+ String thisVid = tvx.id().toString();
+ vtxHash.put(thisVid, tvx);
+
+ // if there are more than one vertexId mapping to the same keyProps -- they are dupes
+ // we dont check till later since a set can contain more than 2.
+ String hKey = getNodeKeyValString( tvx, keyPropNamesArr );
+ if( hKey.equals("") ){
+ // When we have corrupted data, hKey comes back as an empty string
+ // We will just skip this entry since it is not a Dupe - it is
+ // corrupted data which should be picked up in other checks.
+ continue;
+ }
+ if( keyVals2VidHash.containsKey(hKey) ){
+ // We've already seen this key
+ ArrayList <String> tmpVL = (ArrayList <String>)keyVals2VidHash.get(hKey);
+ tmpVL.add(thisVid);
+ keyVals2VidHash.put(hKey, tmpVL);
+ }
+ else {
+ // First time for this key
+ ArrayList <String> tmpVL = new ArrayList <String>();
+ tmpVL.add(thisVid);
+ keyVals2VidHash.put(hKey, tmpVL);
+ }
+ }
+ catch (Exception e) {
+ LOGGER.warn(" >>> Threw an error in getDupeSets4NonDepNodes - just absorb this error and move on. ", e);
+ }
+ }
+
+ for( Map.Entry<String, ArrayList<String>> entry : keyVals2VidHash.entrySet() ){
+ ArrayList <String> vidList = entry.getValue();
+ try {
+ if( !vidList.isEmpty() && vidList.size() > 1 ){
+ // There are more than one vertex id's using the same key info
+ ArrayList <Vertex> vertList = new ArrayList <Vertex> ();
+ for (int i = 0; i < vidList.size(); i++) {
+ String tmpVid = vidList.get(i);
+ vertList.add(vtxHash.get(tmpVid));
+ }
+ returnList.add(vertList);
+ }
+ }
+ catch (Exception e) {
+ LOGGER.warn(" >>> Threw an error in getDupeSets4NonDepNodes - just absorb this error and move on. ", e);
+ }
+
+ }
+ return returnList;
+
+ }// End of getDupeSets4NonDepNodes()
+
+
+ /**
+ * Get values of the key properties for a node as a single string
+ *
+ * @param tvx the vertex to pull the properties from
+ * @param keyPropNamesArr collection of key prop names
+ * @return a String of concatenated values
+ */
+ private String getNodeKeyValString( Vertex tvx,
+ ArrayList <String> keyPropNamesArr ) {
+
+ String retString = "";
+ Iterator <String> propItr = keyPropNamesArr.iterator();
+ while( propItr.hasNext() ){
+ String propName = propItr.next();
+ if( tvx != null ){
+ Object propValObj = tvx.property(propName).orElse(null);
+ if( propValObj == null ){
+ LOGGER.warn(" >>> WARNING >>> could not find this key-property for this vertex. propName = ["
+ + propName + "], VID = " + tvx.id().toString() );
+ }
+ else {
+ retString = " " + retString + propValObj.toString();
+ }
+ }
+ }
+ return retString;
+
+ }// End of getNodeKeyValString()
+
+
+ private String findJustOneUsingIndex( String transId, String fromAppId,
+ GraphTraversalSource gts, HashMap <String,Object> keyPropValsHash,
+ String nType, Long vidAL, Long vidBL, String apiVer){
+
+ // See if querying by JUST the key params (which should be indexed) brings back
+ // ONLY one of the two vertices. Ie. the db still has a pointer to one of them
+ // and the other one is sort of stranded.
+ String returnVid = "";
+
+ try {
+ List <Vertex> tmpVertList = getNodeJustUsingKeyParams( transId, fromAppId, gts,
+ nType, keyPropValsHash, apiVer );
+ if( tmpVertList != null && tmpVertList.size() == 1 ){
+ // We got just one - if it matches one of the ones we're looking
+ // for, then return that VID
+ Vertex tmpV = tmpVertList.get(0);
+ String thisVid = tmpV.id().toString();
+ if( thisVid.equals(vidAL.toString()) || thisVid.equals(vidBL.toString()) ){
+ String msg = " vid = " + thisVid + " is one of two that the DB can retrieve directly ------";
+ //System.out.println(msg);
+ LOGGER.info(msg);
+ returnVid = thisVid;
+ }
+ }
+ }
+ catch ( AAIException ae ){
+ String emsg = "Error trying to get node just by key " + ae.getMessage();
+ //System.out.println(emsg);
+ LOGGER.error(emsg);
+ }
+
+ return returnVid;
+
+ }// End of findJustOneUsingIndex()
+
+} \ No newline at end of file