* ============LICENSE_START=======================================================
* org.onap.aai
* ================================================================================
- * Copyright © 2017 AT&T Intellectual Property. All rights reserved.
+ * 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.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============LICENSE_END=========================================================
- *
- * ECOMP is a trademark and service mark of AT&T Intellectual Property.
*/
package org.onap.aai.dbgen;
import org.apache.tinkerpop.gremlin.structure.VertexProperty;
import org.onap.aai.db.props.AAIProperties;
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.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.serialization.db.AAIDirection;
import org.onap.aai.serialization.db.EdgeProperty;
-import org.onap.aai.util.AAIConfig;
-import org.onap.aai.util.AAIConstants;
-import org.onap.aai.util.FormatDate;
+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 com.thinkaurelius.titan.core.TitanFactory;
-import com.thinkaurelius.titan.core.TitanGraph;
+import org.janusgraph.core.JanusGraphFactory;
+import org.janusgraph.core.JanusGraph;
public class DataGrooming {
- private static EELFLogger LOGGER;
+ private static EELFLogger logger;
private static final String FROMAPPID = "AAI-DB";
private static final String TRANSID = UUID.randomUUID().toString();
private static int dupeGrpsDeleted = 0;
+ private static final String AAI_NODE_TYPE = "aai-node-type";
+ private static final String KEEP_VID_UNDETERMINED ="KeepVid=UNDETERMINED";
/**
* 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());
Properties props = System.getProperties();
props.setProperty(Configuration.PROPERTY_LOGGING_FILE_NAME, AAIConstants.AAI_DATA_GROOMING_LOGBACK_PROPS);
props.setProperty(Configuration.PROPERTY_LOGGING_FILE_PATH, AAIConstants.AAI_HOME_ETC_APP_PROPERTIES);
- LOGGER = EELFManager.getInstance().getLogger(DataGrooming.class);
+ logger = EELFManager.getInstance().getLogger(DataGrooming.class);
String ver = "version"; // Placeholder
Boolean doAutoFix = false;
Boolean edgesOnlyFlag = false;
Boolean neverUseCache = false;
Boolean skipEdgeCheckFlag = false;
+ LoggingContext.init();
+ LoggingContext.partnerName(FROMAPPID);
+ LoggingContext.serviceName(AAIConstants.AAI_RESOURCES_MS);
+ LoggingContext.component("dataGrooming");
+ LoggingContext.targetEntity(AAIConstants.AAI_RESOURCES_MS);
+ LoggingContext.targetServiceName("main");
+ LoggingContext.requestId(TRANSID);
+ LoggingContext.statusCode(StatusCode.COMPLETE);
+ LoggingContext.responseCode(LoggingContext.SUCCESS);
+
int timeWindowMinutes = 0; // A value of 0 means that we will not have a time-window -- we will look
// at all nodes of the passed-in nodeType.
- long windowStartTime = 0; // Translation of the window into a starting timestamp
+
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("") ){
+ if( maxFixStr != null && !maxFixStr.isEmpty() ){
maxRecordsToFix = Integer.parseInt(maxFixStr);
}
String sleepStr = AAIConfig.get("aai.grooming.default.sleep.minutes");
- if( sleepStr != null && !sleepStr.equals("") ){
+ if( sleepStr != null && !sleepStr.isEmpty() ){
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. ");
+ logger.warn("WARNING - could not pick up aai.grooming values from aaiconfig.properties file. ");
}
String prevFileName = "";
dupeGrpsDeleted = 0;
FormatDate fd = new FormatDate("yyyyMMddHHmm", "GMT");
String dteStr = fd.getDateTime();
- String groomOutFileName = "dataGrooming." + dteStr + ".out";
- String argString = "";
- for( int x = 0; x < args.length; x++ ) {
- argString = argString + " " + args[x];
- }
- LOGGER.info(" DataGrooming called with these options: [" + argString + "]");
-
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")) {
+ if ("-edgesOnly".equals(thisArg)) {
edgesOnlyFlag = true;
} else if (thisArg.equals("-autoFix")) {
doAutoFix = true;
- } else if (thisArg.equals("-skipHostCheck")) {
+ } else if ("-skipHostCheck".equals(thisArg)) {
skipHostCheck = true;
- } else if (thisArg.equals("-dontFixOrphans")) {
+ } else if ("-dontFixOrphans".equals(thisArg)) {
dontFixOrphansFlag = true;
- } else if (thisArg.equals("-singleCommits")) {
+ } else if ("-singleCommits".equals(thisArg)) {
singleCommits = true;
- } else if (thisArg.equals("-dupeCheckOff")) {
+ } else if ("-dupeCheckOff".equals(thisArg)) {
dupeCheckOff = true;
- } else if (thisArg.equals("-dupeFixOn")) {
+ } else if ("-dupeFixOn".equals(thisArg)) {
dupeFixOn = true;
- } else if (thisArg.equals("-ghost2CheckOff")) {
+ } else if ("-ghost2CheckOff".equals(thisArg)) {
ghost2CheckOff = true;
- } else if (thisArg.equals("-neverUseCache")) {
+ } else if ("-neverUseCache".equals(thisArg)) {
neverUseCache = true;
- } else if (thisArg.equals("-ghost2FixOn")) {
+ } else if ("-ghost2FixOn".equals(thisArg)) {
ghost2FixOn = true;
- } else if (thisArg.equals("-skipEdgeChecks")) {
+ } else if ("-skipEdgeChecks".equals(thisArg)) {
skipEdgeCheckFlag = true;
- } else if (thisArg.equals("-maxFix")) {
+ } else if ("-maxFix".equals(thisArg)) {
i++;
if (i >= args.length) {
- LOGGER.error(" No value passed with -maxFix option. ");
- System.exit(0);
+ 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) {
- LOGGER.error("Bad value passed with -maxFix option: ["
+ LoggingContext.statusCode(StatusCode.ERROR);
+ LoggingContext.responseCode(LoggingContext.BUSINESS_PROCESS_ERROR);
+ logger.error("Bad value passed with -maxFix option: ["
+ nextArg + "]");
- System.exit(0);
+ AAISystemExitUtil.systemExitCloseAAIGraph(0);
}
} else if (thisArg.equals("-sleepMinutes")) {
i++;
if (i >= args.length) {
- LOGGER.error("No value passed with -sleepMinutes option.");
- System.exit(0);
+ 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) {
- LOGGER.error("Bad value passed with -sleepMinutes option: ["
+ LoggingContext.statusCode(StatusCode.ERROR);
+ LoggingContext.responseCode(LoggingContext.BUSINESS_PROCESS_ERROR);
+ logger.error("Bad value passed with -sleepMinutes option: ["
+ nextArg + "]");
- System.exit(0);
+ AAISystemExitUtil.systemExitCloseAAIGraph(0);
}
- } else if (thisArg.equals("-timeWindowMinutes")) {
+ } else if ("-timeWindowMinutes".equals(thisArg)) {
i++;
if (i >= args.length) {
- LOGGER.error("No value passed with -timeWindowMinutes option.");
- System.exit(0);
+ 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) {
- LOGGER.error("Bad value passed with -timeWindowMinutes option: ["
+ LoggingContext.statusCode(StatusCode.ERROR);
+ LoggingContext.responseCode(LoggingContext.BUSINESS_PROCESS_ERROR);
+ logger.error("Bad value passed with -timeWindowMinutes option: ["
+ nextArg + "]");
- System.exit(0);
+ AAISystemExitUtil.systemExitCloseAAIGraph(0);
}
- 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 );
- }
- } else if (thisArg.equals("-f")) {
+
+ } else if ("-f".equals(thisArg)) {
i++;
if (i >= args.length) {
- LOGGER.error(" No value passed with -f option. ");
- System.exit(0);
+ 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 {
- LOGGER.error(" Unrecognized argument passed to DataGrooming: ["
+ 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 -dupeFixOn -donFixOrphans -sleepMinutes -neverUseCache");
- System.exit(0);
+ 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, AAIProperties.LATEST);
}
catch (Exception ex){
- LOGGER.error("ERROR - Could not create loader", ex);
- System.exit(1);
+ LoggingContext.statusCode(StatusCode.ERROR);
+ LoggingContext.responseCode(LoggingContext.BUSINESS_PROCESS_ERROR);
+ logger.error("ERROR - Could not create loader " + LogFormatTools.getStackTop(ex));
+ AAISystemExitUtil.systemExitCloseAAIGraph(1);
}
+ if (skipHostCheck) {
+ logger.info(" We will skip the HostCheck as requested. ");
+ }
try {
- if (!prevFileName.equals("")) {
+ if (!prevFileName.isEmpty()) {
// They are trying to fix some data based on a data in a
// previous file.
- LOGGER.info(" Call doTheGrooming() with a previous fileName ["
+ logger.info(" Call doTheGrooming() with a previous fileName ["
+ prevFileName + "] for cleanup. ");
Boolean finalShutdownFlag = true;
Boolean cacheDbOkFlag = false;
maxRecordsToFix, groomOutFileName, ver, singleCommits,
dupeCheckOff, dupeFixOn, ghost2CheckOff, ghost2FixOn,
finalShutdownFlag, cacheDbOkFlag,
- skipEdgeCheckFlag, windowStartTime);
+ skipEdgeCheckFlag, timeWindowMinutes);
} else if (doAutoFix) {
// They want us to run the processing twice -- first to look for
// delete candidates, then after
// 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. ");
+ 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, windowStartTime);
+ skipEdgeCheckFlag, timeWindowMinutes);
if (fixCandCount == 0) {
- LOGGER.info(" No fix-Candidates were found by the first pass, so no second/fix-pass is needed. ");
+ 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
+ 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 <<< ");
- System.exit(0);
+ logger.info("\n >>> Sleep Thread has been Interrupted <<< ");
+ AAISystemExitUtil.systemExitCloseAAIGraph(0);
}
dteStr = fd.getDateTime();
String secondGroomOutFileName = "dataGrooming." + dteStr
+ ".out";
- LOGGER.info(" Now, call doTheGrooming() a second time and pass in the name of the file "
+ 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;
secondGroomOutFileName, ver, singleCommits,
dupeCheckOff, dupeFixOn, ghost2CheckOff, ghost2FixOn,
finalShutdownFlag, cacheDbOkFlag,
- skipEdgeCheckFlag, windowStartTime);
+ skipEdgeCheckFlag, timeWindowMinutes);
}
} else {
// Do the grooming - plain vanilla (no fix-it-file, no
// auto-fixing)
Boolean finalShutdownFlag = true;
- LOGGER.info(" Call doTheGrooming() ");
+ logger.info(" Call doTheGrooming() ");
Boolean cacheDbOkFlag = true;
if( neverUseCache ){
// They have forbidden us from using a cached db connection.
maxRecordsToFix, groomOutFileName, ver, singleCommits,
dupeCheckOff, dupeFixOn, ghost2CheckOff, ghost2FixOn,
finalShutdownFlag, cacheDbOkFlag,
- skipEdgeCheckFlag, windowStartTime);
+ skipEdgeCheckFlag, timeWindowMinutes);
}
} catch (Exception ex) {
- LOGGER.error("Exception while grooming data", ex);
+ LoggingContext.statusCode(StatusCode.ERROR);
+ LoggingContext.responseCode(LoggingContext.DATA_ERROR);
+ logger.error("Exception while grooming data " + LogFormatTools.getStackTop(ex));
}
- LOGGER.info(" Done! ");
- System.exit(0);
+ logger.info(" Done! ");
+ AAISystemExitUtil.systemExitCloseAAIGraph(0);
}// End of main()
Boolean dupeCheckOff, Boolean dupeFixOn,
Boolean ghost2CheckOff, Boolean ghost2FixOn,
Boolean finalShutdownFlag, Boolean cacheDbOkFlag,
- Boolean skipEdgeCheckFlag, long windowStartTime) {
+ Boolean skipEdgeCheckFlag, int timeWindowMinutes) {
- LOGGER.debug(" Entering doTheGrooming \n");
+ logger.debug(" Entering doTheGrooming \n");
int cleanupCandidateCount = 0;
+ long windowStartTime = 0; // Translation of the window into a starting timestamp
BufferedWriter bw = null;
- TitanGraph graph = null;
- TitanGraph graph2 = null;
+ JanusGraph graph = null;
+ JanusGraph graph2 = null;
int deleteCount = 0;
boolean executeFinalCommit = false;
Set<String> deleteCandidateList = 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"
// Make sure the target directory exists
new File(targetDir).mkdirs();
- if (!fileNameForFixing.equals("")) {
+ if (!fileNameForFixing.isEmpty()) {
deleteCandidateList = getDeleteList(targetDir,
fileNameForFixing, edgesOnlyFlag, dontFixOrphansFlag,
dupeFixOn);
}
if (deleteCandidateList.size() > maxRecordsToFix) {
- LOGGER.warn(" >> WARNING >> Delete candidate list size ("
+ 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
throw new AAIException("AAI_6124", emsg);
}
- LOGGER.info(" Will write to " + fullOutputFileName );
+ 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");
+ 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
- graph = TitanFactory.open(AAIConstants.CACHED_DB_CONFIG);
+ graph = JanusGraphFactory.open(new AAIGraphConfig.Builder(AAIConstants.CACHED_DB_CONFIG).forService(DataGrooming.class.getSimpleName()).withGraphType("cached").buildConfiguration());
}
else {
- graph = TitanFactory.open(AAIConstants.REALTIME_DB_CONFIG);
+ graph = JanusGraphFactory.open(new AAIGraphConfig.Builder(AAIConstants.REALTIME_DB_CONFIG).forService(DataGrooming.class.getSimpleName()).withGraphType("realtime1").buildConfiguration());
}
if (graph == null) {
String emsg = "null graph object in DataGrooming\n";
throw new AAIException("AAI_6101", emsg);
}
- LOGGER.debug(" Got the graph object. ");
+ logger.debug(" Got the graph object. ");
g = graph.newTransaction();
if (g == null) {
Set<Entry<String, Introspector>> entrySet = loader.getAllObjects().entrySet();
String ntList = "";
- LOGGER.info(" Starting DataGrooming Processing ");
+ logger.info(" Starting DataGrooming Processing ");
if (edgesOnlyFlag) {
- LOGGER.info(" NOTE >> Skipping Node processing as requested. Will only process Edges. << ");
+ 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;
- LOGGER.debug(" > Look at : [" + nType + "] ...");
+
+ 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
- Collection <String> keyProps = entry.getValue().getKeys();
+ // 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);
// 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);
+ 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());
thisNtCount++;
if( thisNtCount == lastShownForNt + 250 ){
lastShownForNt = thisNtCount;
- LOGGER.debug("count for " + nType + " so far = " + thisNtCount );
+ logger.debug("count for " + nType + " so far = " + thisNtCount );
}
Vertex thisVtx = iter.next();
if( windowStartTime > 0 ){
- // We only want nodes that are created after a passed-in timestamp
- Object objTimeStamp = thisVtx.property("aai-created-ts").orElse(null);
- if( objTimeStamp != null ){
- long thisNodeCreateTime = (long)objTimeStamp;
- if( thisNodeCreateTime < windowStartTime ){
- // It is NOT in our window, so we can pass over it
+ // 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);
+ logger.debug("skipping already processed vertex: " + thisVid);
continue;
}
totalNodeCount++;
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 and that we only get one.
+ // 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 );
}
pCount++;
}
if( pCount <= 0 ){
-
-
- //List<Vertex> vertI2 = g.traversal().V(thisVtx).union(__.outE().has("isParent-REV",true).outV(),__.inE().has("isParent",true).inV()).toList();
- //if( vertI2.isEmpty()){
// It's Missing it's dependent/parent node
depNodeOk = false;
zeroEdges = true;
}
} catch (Exception ex) {
- LOGGER.warn("WARNING from inside the for-each-vid-loop orphan-edges-check ", ex);
+ LoggingContext.statusCode(StatusCode.ERROR);
+ LoggingContext.responseCode(LoggingContext.DATA_ERROR);
+ logger.warn("WARNING from inside the for-each-vid-loop orphan-edges-check " + LogFormatTools.getStackTop(ex) );
}
if (deleteCandidateList.contains(thisVid)) {
thisNtDeleteCount++;
} catch (Exception e) {
okFlag = false;
- LOGGER.error("ERROR trying to delete missing-dep-node VID = " + thisVid, e);
+ LoggingContext.statusCode(StatusCode.ERROR);
+ LoggingContext.responseCode(LoggingContext.DATA_ERROR);
+ logger.error("ERROR trying to delete missing-dep-node VID = " + thisVid + " " + LogFormatTools.getStackTop(e));
}
if (okFlag) {
- LOGGER.info(" DELETED missing-dep-node VID = " + thisVid);
+ logger.info(" DELETED missing-dep-node VID = " + thisVid);
}
} else {
// We count nodes missing their depNodes two ways - the first if it has
}
}
}
- }
+ }// end of -- else this is a dependent node -- piece
- if( depNodeOk && (secondGetList == null || secondGetList.size() == 0) ){
+ if( depNodeOk && (secondGetList == null || secondGetList.isEmpty()) ){
// We could not get the node back using it's own key info.
// So, it's a PHANTOM
if (deleteCandidateList.contains(thisVid)) {
thisNtDeleteCount++;
} catch (Exception e) {
okFlag = false;
- LOGGER.error("ERROR trying to delete phantom VID = " + thisVid, e);
+ LoggingContext.statusCode(StatusCode.ERROR);
+ LoggingContext.responseCode(LoggingContext.DATA_ERROR);
+ logger.error("ERROR trying to delete phantom VID = " + thisVid + " " + LogFormatTools.getStackTop(e));
}
if (okFlag) {
- LOGGER.info(" DELETED VID = " + thisVid);
+ 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 - ");
+ logger.info(" - now check Dupes for this guy - ");
List<String> tmpDupeGroups = checkAndProcessDupes(
TRANSID, FROMAPPID, g, source1, version,
nType, secondGetList, dupeFixOn,
while (dIter.hasNext()) {
// Add in any newly found dupes to our running list
String tmpGrp = dIter.next();
- LOGGER.info("Found set of dupes: [" + tmpGrp + "]");
+ logger.info("Found set of dupes: [" + tmpGrp + "]");
dupeGroups.add(tmpGrp);
}
}
}
catch (AAIException e1) {
- LOGGER.warn(" For nodeType = " + nType + " Caught exception", 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) {
- LOGGER.warn(" For nodeType = " + nType
+ 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 of a single vertex
+ }// try block to enclose looping over each single vertex
catch (Exception exx) {
- LOGGER.warn("WARNING from inside the while-verts-loop ", 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();
}
thisNtDeleteCount = 0;
- LOGGER.info( " Processed " + thisNtCount + " records for [" + nType + "], " + totalNodeCount + " total overall. " );
+ logger.info( " Processed " + thisNtCount + " records for [" + nType + "], " + totalNodeCount + " total overall. " );
}// While-loop for each node type
+
}// end of check to make sure we weren't only supposed to do edges
// --------------------------------------------------------------------------------------
// 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");
+ 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.
- graph2 = TitanFactory.open(AAIConstants.REALTIME_DB_CONFIG);
+ graph2 = JanusGraphFactory.open(new AAIGraphConfig.Builder(AAIConstants.REALTIME_DB_CONFIG).forService(DataGrooming.class.getSimpleName()).withGraphType("realtime2").buildConfiguration());
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");
+ logger.debug("Got the graph2 object... \n");
}
g2 = graph2.newTransaction();
if (g2 == null) {
int counter = 0;
int lastShown = 0;
Iterator<Vertex> vItor2 = vertList.iterator();
- LOGGER.info(" Checking for bad edges --- ");
+ logger.info(" Checking for bad edges --- ");
while (vItor2.hasNext()) {
Vertex v = null;
try {
v = vItor2.next();
} catch (Exception vex) {
- LOGGER.warn(">>> WARNING trying to get next vertex on the vItor2 ");
+ LoggingContext.statusCode(StatusCode.ERROR);
+ LoggingContext.responseCode(LoggingContext.DATA_ERROR);
+ logger.warn(">>> WARNING trying to get next vertex on the vItor2 ");
continue;
}
try {
thisVertId = v.id().toString();
} catch (Exception ev) {
- LOGGER.warn("WARNING when doing getId() on a vertex from our vertex list. ");
+ 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 = "
+ logger.info(" >> Skipping edge check for edges from vertexId = "
+ thisVertId
+ ", since that guy is a Phantom Node");
continue;
}
if( windowStartTime > 0 ){
- // We only want to look at nodes that are created after a passed-in timestamp
- Object objTimeStamp = v.property("aai-created-ts").orElse(null);
- if( objTimeStamp != null ){
- long thisNodeCreateTime = (long)objTimeStamp;
- if( thisNodeCreateTime < windowStartTime ){
- // It is NOT in our window, so we can pass over it
+ // 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 # "
+ logger.info("... Checking edges for vertex # "
+ counter);
}
Iterator<Edge> eItor = v.edges(Direction.BOTH);
try {
e = eItor.next();
} catch (Exception iex) {
- LOGGER.warn(">>> WARNING trying to get next edge on the eItor ", 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) {
- LOGGER.warn(">>> WARNING trying to get edge's In-vertex ", 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 = "";
Boolean cantGetUsingVid = false;
if (vIn != null) {
try {
- Object ob = vIn.<Object>property("aai-node-type").orElse(null);
+ Object ob = vIn.<Object>property(AAI_NODE_TYPE).orElse(null);
if (ob != null) {
vNtI = ob.toString();
keysMissing = anyKeyFieldsMissing(vNtI, vIn, loader);
if( ! ghost2CheckOff ){
Vertex connectedVert = g2.traversal().V(vIdLong).next();
if( connectedVert == null ) {
- LOGGER.warn( "GHOST2 -- got NULL when doing getVertex for vid = " + vIdLong);
+ 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,
ghost2 = g.traversal().V(vIdLong).next();
}
catch( Exception ex){
- LOGGER.warn( "GHOST2 -- Could not get the ghost info for a bad edge for vtxId = " + vIdLong, 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) {
- LOGGER.warn(">>> WARNING trying to get edge's In-vertex props ", 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("")
deleteCount++;
} catch (Exception e1) {
okFlag = false;
- LOGGER.warn("WARNING when trying to delete bad-edge-connected VERTEX VID = "
+ 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 = "
+ logger.info(" DELETED vertex from bad edge = "
+ vIdI);
}
} else {
// that this edge has already been
// removed
okFlag = false;
- LOGGER.warn("WARNING when trying to delete edge = "
+ 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);
+ logger.info(" DELETED edge = " + thisEid);
}
}
} else {
try {
vOut = e.outVertex();
} catch (Exception err) {
- LOGGER.warn(">>> WARNING trying to get edge's Out-vertex ");
+ LoggingContext.statusCode(StatusCode.ERROR);
+ LoggingContext.responseCode(LoggingContext.DATA_ERROR);
+ logger.warn(">>> WARNING trying to get edge's Out-vertex ");
}
String vNtO = "";
String vIdO = "";
cantGetUsingVid = false;
if (vOut != null) {
try {
- Object ob = vOut.<Object>property("aai-node-type").orElse(null);
+ Object ob = vOut.<Object>property(AAI_NODE_TYPE).orElse(null);
if (ob != null) {
vNtO = ob.toString();
keysMissing = anyKeyFieldsMissing(vNtO,
Vertex connectedVert = g2.traversal().V(vIdLong).next();
if( connectedVert == null ) {
cantGetUsingVid = true;
- LOGGER.info( "GHOST2 -- got NULL when doing getVertex for vid = " + vIdLong);
+ 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){
- LOGGER.warn( "GHOST2 -- Could not get the ghost info for a bad edge for vtxId = " + vIdLong, 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) {
- LOGGER.warn(">>> WARNING trying to get edge's Out-vertex props ", 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("")
+ if (keysMissing || vOut == null || vNtO.isEmpty()
|| 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("")) {
+ if (!vIdO.isEmpty()) {
// try to get rid of the corrupted vertex
try {
if( (ghost2 != null) && ghost2FixOn ){
deleteCount++;
} catch (Exception e1) {
okFlag = false;
- LOGGER.warn("WARNING when trying to delete bad-edge-connected VID = "
+ 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 = "
+ logger.info(" DELETED vertex from bad edge = "
+ vIdO);
}
} else {
// that this edge has already been
// removed
okFlag = false;
- LOGGER.warn("WARNING when trying to delete edge = "
+ 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);
+ logger.info(" DELETED edge = " + thisEid);
}
}
} else {
}
}// End of while-edges-loop
} catch (Exception exx) {
- LOGGER.warn("WARNING from in the while-verts-loop ", 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)
} // end of -- if we're not skipping the edge-checking
deleteCount = deleteCount + dupeGrpsDeleted;
if (!singleCommits && deleteCount > 0) {
try {
- LOGGER.info("About to do the commit for "
+ logger.info("About to do the commit for "
+ deleteCount + " removes. ");
executeFinalCommit = true;
- LOGGER.info("Commit was successful ");
+ logger.info("Commit was successful ");
} catch (Exception excom) {
- LOGGER.error(" >>>> ERROR <<<< Could not commit changes. ", excom);
+ LoggingContext.statusCode(StatusCode.ERROR);
+ LoggingContext.responseCode(LoggingContext.DATA_ERROR);
+ logger.error(" >>>> ERROR <<<< Could not commit changes. " + LogFormatTools.getStackTop(excom));
deleteCount = 0;
}
}
deleteCount = deleteCount + dupeGrpsDeleted;
bw.write("\n\n ============ Summary ==============\n");
- bw.write("Ran these nodeTypes: " + ntList + "\n\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(info + "\n");
}
} catch (Exception dex) {
- LOGGER.error("error trying to print detail info for a ghost-node: ", 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(info + "\n");
}
} catch (Exception dex) {
- LOGGER.error("error trying to print detail info for a Orphan Node /missing dependent edge", 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(info + "\n");
}
} catch (Exception dex) {
- LOGGER.error("error trying to print detail info for a node missing its dependent edge but not an orphan", dex);
+ LoggingContext.statusCode(StatusCode.ERROR);
+ LoggingContext.responseCode(LoggingContext.DATA_ERROR);
+ logger.error("error trying to print detail info for a node missing its dependent edge but not an orphan "
+ + LogFormatTools.getStackTop(dex));
}
}
+ propKey.value() + "]\n");
}
} catch (Exception pex) {
- LOGGER.error("error trying to print empty/bad vertex data: ", pex);
+ LoggingContext.statusCode(StatusCode.ERROR);
+ LoggingContext.responseCode(LoggingContext.DATA_ERROR);
+ logger.error("error trying to print empty/bad vertex data: " + LogFormatTools.getStackTop(pex));
}
}
// This is the last entry which should tell us if we
// have a preferred keeper
String prefString = dupeArr[i];
- if (prefString.equals("KeepVid=UNDETERMINED")) {
+ if (KEEP_VID_UNDETERMINED.equals(prefString)) {
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 {
// should look like, "KeepVid=12345"
String[] prefArr = prefString.split("=");
if (prefArr.length != 2
- || (!prefArr[0].equals("KeepVid"))) {
+ || (!"KeepVid".equals(prefArr[0]))) {
throw new Exception("Bad format. Expecting KeepVid=999999");
} else {
String keepVidStr = prefArr[1];
}// else last entry
}// for each vertex in a group
} catch (Exception dex) {
- LOGGER.error("error trying to print duplicate vertex data", 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 ------------- Got these errors while processing: \n");
Iterator<String> errIter = errArr.iterator();
while (errIter.hasNext()) {
- String line = (String) errIter.next();
+ String line = errIter.next();
bw.write(line + "\n");
}
bw.close();
- LOGGER.info("\n ------------- Done doing all the checks ------------ ");
- LOGGER.info("Output will be written to " + fullOutputFileName);
+ 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
+ "] and investigate delete candidates. ");
}
} catch (AAIException e) {
- LOGGER.error("Caught AAIException while grooming data", e);
+ LoggingContext.statusCode(StatusCode.ERROR);
+ LoggingContext.responseCode(LoggingContext.DATA_ERROR);
+ logger.error("Caught AAIException while grooming data");
ErrorLogHelper.logException(e);
} catch (Exception ex) {
- LOGGER.error("Caught exception while grooming data", 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 {
try {
bw.close();
} catch (IOException iox) {
- LOGGER.warn("Got an IOException trying to close bufferedWriter() \n", iox);
+ LoggingContext.statusCode(StatusCode.ERROR);
+ LoggingContext.responseCode(LoggingContext.AVAILABILITY_TIMEOUT_ERROR);
+ logger.warn("Got an IOException trying to close bufferedWriter() \n", iox);
}
}
}
g.tx().rollback();
} catch (Exception ex) {
- // Don't throw anything because Titan sometimes is just saying that the graph is already closed
- LOGGER.warn("WARNING from final graphTransaction.rollback()", 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);
}
}
try {
g2.tx().rollback();
} catch (Exception ex) {
- // Don't throw anything because Titan sometimes is just saying that the graph is already closed
- LOGGER.warn("WARNING from final graphTransaction2.rollback()", 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);
}
}
graph.close();
}
} catch (Exception ex) {
- // Don't throw anything because Titan sometimes is just saying that the graph is already closed{
- LOGGER.warn("WARNING from final graph.shutdown()", 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 {
graph2.close();
}
} catch (Exception ex) {
- // Don't throw anything because Titan sometimes is just saying that the graph is already closed{
- LOGGER.warn("WARNING from final graph2.shutdown()", 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);
}
}
while (keyPropI.hasNext()) {
String propName = keyPropI.next();
Object ob = v.<Object>property(propName).orElse(null);
- if (ob == null || ob.toString().equals("")) {
+ if (ob == null || ob.toString().isEmpty()) {
// It is missing a key property
return true;
}
String vtxANodeType = "";
String vtxBNodeType = "";
- Object objType = vtxA.<Object>property("aai-node-type").orElse(null);
+ 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);
+ objType = vtxB.<Object>property(AAI_NODE_TYPE).orElse(null);
if (objType != null) {
vtxBNodeType = objType.toString();
}
- if (vtxANodeType.equals("") || (!vtxANodeType.equals(vtxBNodeType))) {
+ if (vtxANodeType.isEmpty() || (!vtxANodeType.equals(vtxBNodeType))) {
// Either they're not really dupes or there's some bad data - so
// don't pick one
return nullVtx;
// (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);
+ logger.warn("Required property not found", e);
throw new AAIException("AAI_6105", "Required Property name(s) not found for nodeType = " + vtxANodeType + ")");
}
// 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
Vertex tvCon = iter.next();
String conVid = tvCon.id().toString();
String nt = "";
- objType = tvCon.<Object>property("aai-node-type").orElse(null);
+ objType = tvCon.<Object>property(AAI_NODE_TYPE).orElse(null);
if (objType != null) {
nt = objType.toString();
}
Vertex tvCon = iter.next();
String conVid = tvCon.id().toString();
String nt = "";
- objType = tvCon.<Object>property("aai-node-type").orElse(null);
+ objType = tvCon.<Object>property(AAI_NODE_TYPE).orElse(null);
if (objType != null) {
nt = objType.toString();
}
}
// 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)
+ // 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.
- Collection <String> depNodeTypes = loader.introspectorFromName(vtxANodeType).getDependentOn();
-
+ // 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 = "";
depNodeVtxId4B = nodeTypesConn2B.get(depNodeType);
}
}
- if (depNodeVtxId4A.equals("")
+ if (depNodeVtxId4A.isEmpty()
|| (!depNodeVtxId4A.equals(depNodeVtxId4B))) {
// Either they're not really dupes or there's some bad data - so
// don't pick either one
}
if (vtxIdsConn2A.size() == vtxIdsConn2B.size()) {
- // 2 - If they both have edges to all the same vertices, then return
- // the one with the lower vertexId.
+ // 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()) {
}
if (allTheSame) {
- if (vidA < vidB) {
+ // 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.isEmpty() ){
+ // 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.
+ // 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()) {
}
}
if (!missingOne) {
- preferredVtx = vtxA;
+ if( onlyNodeThatIndexPointsToVidStr.isEmpty()
+ || 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.
+ // 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()) {
}
}
if (!missingOne) {
- preferredVtx = vtxB;
+ if( onlyNodeThatIndexPointsToVidStr.isEmpty()
+ || onlyNodeThatIndexPointsToVidStr.equals(vidB.toString()) ){
+ preferredVtx = vtxB;
+ }
}
} else {
preferredVtx = nullVtx;
* @param deleteCandidateList the delete candidate list
* @param singleCommits the single commits
* @param alreadyFoundDupeGroups the already found dupe groups
- * @param dbMaps the db maps
* @return the array list
*/
private static List<String> checkAndProcessDupes(String transId,
String dupesStr = "";
for (int i = 0; i < checkVertList.size(); i++) {
dupesStr = dupesStr
- + ((checkVertList.get(i))).id()
- .toString() + "|";
+ + 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";
+ dupesStr = dupesStr + KEEP_VID_UNDETERMINED;
returnList.add(dupesStr);
} else {
dupesStr = dupesStr + "KeepVid=" + prefV.id();
String dupesStr = "";
for (int i = 0; i < thisParentsVertList.size(); i++) {
dupesStr = dupesStr
- + ((thisParentsVertList
- .get(i))).id() + "|";
+ + thisParentsVertList.get(i).id() + "|";
}
if (dupesStr != "") {
Vertex prefV = getPreferredDupe(transId,
if (prefV == null) {
// We could not determine which duplicate to
// keep
- dupesStr = dupesStr + "KeepVid=UNDETERMINED";
+ dupesStr = dupesStr + KEEP_VID_UNDETERMINED;
returnList.add(dupesStr);
} else {
Boolean didRemove = false;
}
}
} catch (Exception e) {
- LOGGER.warn(" >>> Threw an error in checkAndProcessDupes - just absorb this error and move on. ", 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;
* @param version the version
* @param nType the n type
* @param passedVertList the passed vert list
- * @param dbMaps the db maps
* @return the hash map
* @throws AAIException the AAI exception
*/
String transId, String fromAppId, GraphTraversalSource g, String version,
String nType, ArrayList<Vertex> passedVertList, Loader loader)
throws AAIException {
- // Given a list of Titan Vertices of one nodeType (see AAI-8956), group
+ // 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.
Vertex tmpParentVtx = getConnectedParent( g, thisVert );
if( tmpParentVtx != null ) {
String parentNt = null;
- Object obj = tmpParentVtx.<Object>property("aai-node-type").orElse(null);
+ Object obj = tmpParentVtx.<Object>property(AAI_NODE_TYPE).orElse(null);
if (obj != null) {
parentNt = obj.toString();
}
// 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) {
+ if (deleteCandidateList == null || deleteCandidateList.isEmpty()) {
// No vid's on the candidate list -- so no deleting will happen on
// this run
return false;
// This is the last entry which should tell us if we have a
// preferred keeper
String prefString = dupeArr[i];
- if (prefString.equals("KeepVid=UNDETERMINED")) {
+ if (prefString.equals(KEEP_VID_UNDETERMINED)) {
// They sent us a bad string -- nothing should be deleted if
// no dupe could be tagged as preferred
return false;
// 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"))) {
- LOGGER.error("Bad format. Expecting KeepVid=999999");
+ if (prefArr.length != 2 || (!"KeepVid".equals(prefArr[0]))) {
+ LoggingContext.statusCode(StatusCode.ERROR);
+ LoggingContext.responseCode(LoggingContext.DATA_ERROR);
+ logger.error("Bad format. Expecting KeepVid=999999");
return false;
} else {
String keepVidStr = prefArr[1];
}
} catch (Exception e) {
okFlag = false;
- LOGGER.error("ERROR trying to delete VID = " + thisVid, e);
+ 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);
+ logger.info(" DELETED VID = " + thisVid);
deletedSomething = true;
}
}
}
} else {
- LOGGER.error("ERROR - Vertex Id to keep not found in list of dupes. dupeInfoString = ["
+ 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;
}
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);
+ 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);
+ 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);
- }
+ 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);
+ 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 ){
- LOGGER.error( " ERROR trying to get node for: [" + propsAndValuesForMsg + "]", 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 ){
}
}
- if( retVertList.size() == 0 ){
- LOGGER.debug("DEBUG No node found for nodeType = [" + nodeType +
+ if( retVertList.isEmpty() ){
+ logger.debug("DEBUG No node found for nodeType = [" + nodeType +
"], propsAndVal = " + propsAndValuesForMsg );
}
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 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 );
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 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 );
}
}
else {
String nodeType = "";
- Object ob = tVert.<Object>property("aai-node-type").orElse(null);
+ Object ob = tVert.<Object>property(AAI_NODE_TYPE).orElse(null);
if( ob == null ){
nodeType = "null";
}
private static ArrayList <Vertex> getConnectedChildrenOfOneType( GraphTraversalSource g,
- Vertex startVtx, String childNType ) throws AAIException{
+ Vertex startVtx, String childNType ) {
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);
+ Object ob = tmpVtx.<Object>property(AAI_NODE_TYPE).orElse(null);
if (ob != null) {
String tmpNt = ob.toString();
if( tmpNt.equals(childNType)){
private static Vertex getConnectedParent( GraphTraversalSource g,
- Vertex startVtx ) throws AAIException{
+ Vertex startVtx ) {
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();
long unixTimeNow = System.currentTimeMillis();
long windowInMillis = timeWindowMinutes * 60L * 1000;
- long startTimeStamp = unixTimeNow - windowInMillis;
-
- return startTimeStamp;
+ return unixTimeNow - windowInMillis;
} // 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 static 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( 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 static 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);
+ retString = " " + retString + propValObj.toString();
+ }
+ }
+ return retString;
+
+ }// End of getNodeKeyValString()
+
+
+ private static 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 ------";
+
+ logger.info(msg);
+ returnVid = thisVid;
+ }
+ }
+ }
+ catch ( AAIException ae ){
+ String emsg = "Error trying to get node just by key " + ae.getMessage();
+
+ logger.error(emsg);
+ }
+
+ return returnVid;
+
+ }// End of findJustOneUsingIndex()
+
}