From c2ccad8c88d6eec83e8abb0eb4bab6f65cb74912 Mon Sep 17 00:00:00 2001 From: "Kajur, Harish (vk250x)" Date: Thu, 1 Nov 2018 17:46:56 -0400 Subject: Add capability to record timestamp Issue-ID: AAI-1831 Change-Id: Ifcadf1caa0f7764c759997f10f3f789b1a702872 Signed-off-by: Kajur, Harish (vk250x) --- .../aai/serialization/queryformats/Format.java | 3 +- .../serialization/queryformats/FormatFactory.java | 3 + .../queryformats/ResourceWithSoT.java | 166 +++++++++++++++++++ .../queryformats/ResourceWithSoTTest.java | 181 +++++++++++++++++++++ 4 files changed, 352 insertions(+), 1 deletion(-) create mode 100644 aai-core/src/main/java/org/onap/aai/serialization/queryformats/ResourceWithSoT.java create mode 100644 aai-core/src/test/java/org/onap/aai/serialization/queryformats/ResourceWithSoTTest.java diff --git a/aai-core/src/main/java/org/onap/aai/serialization/queryformats/Format.java b/aai-core/src/main/java/org/onap/aai/serialization/queryformats/Format.java index abd261ed..7e7356fe 100644 --- a/aai-core/src/main/java/org/onap/aai/serialization/queryformats/Format.java +++ b/aai-core/src/main/java/org/onap/aai/serialization/queryformats/Format.java @@ -31,7 +31,8 @@ public enum Format { resource_and_url, console, raw, - count; + count, + resource_with_sot; public static Format getFormat(String format) throws AAIException { try { diff --git a/aai-core/src/main/java/org/onap/aai/serialization/queryformats/FormatFactory.java b/aai-core/src/main/java/org/onap/aai/serialization/queryformats/FormatFactory.java index 29ae8512..f36941f8 100644 --- a/aai-core/src/main/java/org/onap/aai/serialization/queryformats/FormatFactory.java +++ b/aai-core/src/main/java/org/onap/aai/serialization/queryformats/FormatFactory.java @@ -83,6 +83,9 @@ public class FormatFactory { case count : formatter = new Formatter(inject(new Count(), params)); break; + case resource_with_sot : + formatter = new Formatter(inject(new ResourceWithSoT.Builder(loader, serializer, urlBuilder), params).build()); + break; default : break; diff --git a/aai-core/src/main/java/org/onap/aai/serialization/queryformats/ResourceWithSoT.java b/aai-core/src/main/java/org/onap/aai/serialization/queryformats/ResourceWithSoT.java new file mode 100644 index 00000000..503f3a58 --- /dev/null +++ b/aai-core/src/main/java/org/onap/aai/serialization/queryformats/ResourceWithSoT.java @@ -0,0 +1,166 @@ +/** + * ============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.serialization.queryformats; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import org.apache.tinkerpop.gremlin.structure.Vertex; +import org.onap.aai.db.props.AAIProperties; +import org.onap.aai.introspection.Loader; +import org.onap.aai.serialization.db.DBSerializer; +import org.onap.aai.serialization.queryformats.exceptions.AAIFormatVertexException; +import org.onap.aai.serialization.queryformats.params.Depth; +import org.onap.aai.serialization.queryformats.params.NodesOnly; +import org.onap.aai.serialization.queryformats.utils.UrlBuilder; +import org.onap.aai.util.AAIConfig; + +import java.util.Optional; + +public class ResourceWithSoT extends MultiFormatMapper { + protected JsonParser parser = new JsonParser(); + protected final DBSerializer serializer; + protected final Loader loader; + protected final UrlBuilder urlBuilder; + protected final int depth; + protected final boolean nodesOnly; + protected ResourceWithSoT(Builder builder) { + this.urlBuilder = builder.getUrlBuilder(); + this.loader = builder.getLoader(); + this.serializer = builder.getSerializer(); + this.depth = builder.getDepth(); + this.nodesOnly = builder.isNodesOnly(); + } + + @Override + public int parallelThreshold() { + return 100; + } + + public static class Builder implements NodesOnly, Depth { + + protected final Loader loader; + protected final DBSerializer serializer; + protected final UrlBuilder urlBuilder; + protected boolean includeUrl = false; + protected boolean nodesOnly = false; + protected int depth = 1; + protected boolean modelDriven = false; + public Builder(Loader loader, DBSerializer serializer, UrlBuilder urlBuilder) { + this.loader = loader; + this.serializer = serializer; + this.urlBuilder = urlBuilder; + } + + protected Loader getLoader() { + return this.loader; + } + + protected DBSerializer getSerializer() { + return this.serializer; + } + + protected UrlBuilder getUrlBuilder() { + return this.urlBuilder; + } + + public Builder includeUrl() { + this.includeUrl = true; + return this; + } + + public Builder nodesOnly(Boolean nodesOnly) { + this.nodesOnly = nodesOnly; + return this; + } + public boolean isNodesOnly() { + return this.nodesOnly; + } + + public Builder depth(Integer depth) { + this.depth = depth; + return this; + } + + public int getDepth() { + return this.depth; + } + + public boolean isIncludeUrl() { + return this.includeUrl; + } + + public Builder modelDriven() { + this.modelDriven = true; + return this; + } + + public boolean getModelDriven() { + return this.modelDriven; + } + public ResourceWithSoT build() { + return new ResourceWithSoT(this); + } + } + + /** + * + * Returns an Optional to convert the contents from the given Vertex object into a JsonObject. + * The fields returned are to record the time stamp of the creation/modification of the object, the user responsible for + * the change, and the last http method performed on the object. + * + * @param v + * @return + * @throws AAIFormatVertexException + */ + @Override + protected Optional getJsonFromVertex(Vertex v) throws AAIFormatVertexException { + // Null check + if (v == null) + return null; + + JsonObject json = new JsonObject(); + + Object createdTimestampObj = v.property(AAIProperties.CREATED_TS).value(); + Object lastModifiedTimestampObj = v.property(AAIProperties.LAST_MOD_TS).value(); + Object sotObj = v.property(AAIProperties.SOURCE_OF_TRUTH).value(); + Object lastModSotObj = v.property(AAIProperties.LAST_MOD_SOURCE_OF_TRUTH).value(); + long createdTimestamp = Long.valueOf(createdTimestampObj.toString()); + long lastModifiedTimestamp = Long.valueOf(lastModifiedTimestampObj.toString()); + long threshold = Long.valueOf(AAIConfig.get("aai.resource.format.threshold", "10")); + + // Add to the property field of the JSON payload + json.addProperty("aai-created-ts", createdTimestampObj.toString()); + json.addProperty("aai-last-mod-ts", lastModifiedTimestampObj.toString()); + json.addProperty("source-of-truth", sotObj.toString()); + json.addProperty("last-mod-source-of-truth", lastModSotObj.toString()); + + // Check if the timestamp difference between creation and last modification are greater than a certain threshold, and if the source of truth differs + // If the timestamp difference is marginal and the SoT (creator/modifier) is the same, the last action performed is likely to be a creation. + long timestampDiff = lastModifiedTimestamp - createdTimestamp; + boolean isSameSoT = sotObj.toString().equals(lastModSotObj.toString()); + + if (timestampDiff <= threshold && isSameSoT) + json.addProperty("last-action-performed", "Created"); + else + json.addProperty("last-action-performed", "Modified"); + + return Optional.of(json); + } +} diff --git a/aai-core/src/test/java/org/onap/aai/serialization/queryformats/ResourceWithSoTTest.java b/aai-core/src/test/java/org/onap/aai/serialization/queryformats/ResourceWithSoTTest.java new file mode 100644 index 00000000..ad28e697 --- /dev/null +++ b/aai-core/src/test/java/org/onap/aai/serialization/queryformats/ResourceWithSoTTest.java @@ -0,0 +1,181 @@ +/** + * ============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.serialization.queryformats; + +import com.google.gson.JsonObject; +import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource; +import org.apache.tinkerpop.gremlin.process.traversal.strategy.verification.ReadOnlyStrategy; +import org.apache.tinkerpop.gremlin.structure.Graph; +import org.apache.tinkerpop.gremlin.structure.Vertex; +import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerGraph; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.onap.aai.AAISetup; +import org.onap.aai.dbmap.DBConnectionType; +import org.onap.aai.exceptions.AAIException; +import org.onap.aai.introspection.Loader; +import org.onap.aai.introspection.ModelType; +import org.onap.aai.serialization.db.DBSerializer; +import org.onap.aai.serialization.engines.JanusGraphDBEngine; +import org.onap.aai.serialization.engines.QueryStyle; +import org.onap.aai.serialization.engines.TransactionalGraphEngine; +import org.onap.aai.serialization.queryformats.exceptions.AAIFormatVertexException; +import org.onap.aai.serialization.queryformats.utils.UrlBuilder; +import org.onap.aai.setup.SchemaVersion; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +public class ResourceWithSoTTest extends AAISetup { + @Mock + private UrlBuilder urlBuilder; + + private Graph graph; + private Vertex putVertex; + private Vertex patchVertex1; + private Vertex patchVertex2; + + private JsonObject jsonPutObj = new JsonObject() ; + private JsonObject jsonPatchObj1 = new JsonObject() ; + private JsonObject jsonPatchObj2 = new JsonObject() ; + + private SchemaVersion version; + private ResourceWithSoT resourceWithSoT; + + private TransactionalGraphEngine dbEngine; + private Loader loader; + private DBSerializer serializer; + private final ModelType factoryType = ModelType.MOXY; + + @Before + public void setUp() throws Exception { + + version = schemaVersions.getDefaultVersion(); + MockitoAnnotations.initMocks(this); + + graph = TinkerGraph.open(); + + Long currentTimeMs = System.currentTimeMillis(); + String timeNowInMs = Long.toString(currentTimeMs); + + // PUT / CREATE + jsonPutObj.addProperty("aai-created-ts", timeNowInMs); + jsonPutObj.addProperty("aai-last-mod-ts", timeNowInMs); + jsonPutObj.addProperty("source-of-truth", "user_a"); + jsonPutObj.addProperty("last-mod-source-of-truth", "user_a"); + jsonPutObj.addProperty("last-action-performed", "Created"); + + putVertex = graph.addVertex( + "aai-created-ts", timeNowInMs, + "aai-last-mod-ts", timeNowInMs, + "source-of-truth", "user_a", + "last-mod-source-of-truth", "user_a" + ); + + // PATCH / MODIFY with differing source of truths + jsonPatchObj1.addProperty("aai-created-ts", timeNowInMs); + jsonPatchObj1.addProperty("aai-last-mod-ts", timeNowInMs); + jsonPatchObj1.addProperty("source-of-truth", "user_a"); + jsonPatchObj1.addProperty("last-mod-source-of-truth", "user_b"); + jsonPatchObj1.addProperty("last-action-performed", "Modified"); + + patchVertex1 = graph.addVertex( + "aai-created-ts", timeNowInMs, + "aai-last-mod-ts", timeNowInMs, + "source-of-truth", "user_a", + "last-mod-source-of-truth", "user_b" + ); + + // PATCH / MODIFY with differing time stamps + jsonPatchObj2.addProperty("aai-created-ts", timeNowInMs); + jsonPatchObj2.addProperty("aai-last-mod-ts", Long.toString(currentTimeMs + 1000)); + jsonPatchObj2.addProperty("source-of-truth", "user_a"); + jsonPatchObj2.addProperty("last-mod-source-of-truth", "user_a"); + jsonPatchObj2.addProperty("last-action-performed", "Modified"); + + patchVertex2 = graph.addVertex( + "aai-created-ts", timeNowInMs, + "aai-last-mod-ts", Long.toString(currentTimeMs + 1000), + "source-of-truth", "user_a", + "last-mod-source-of-truth", "user_a" + ); + + graph = TinkerGraph.open(); + createLoaderEngineSetup(); + } + + // This test is to simulate a PUT request + @Test + public void testGetJsonFromVertexWithCreateVertex() throws AAIFormatVertexException, AAIException { + if (putVertex == null) + assertTrue("The vertex used for this test is null. Fail immediately.", false); + + JsonObject json = resourceWithSoT.getJsonFromVertex(putVertex).get(); + assertEquals(jsonPutObj, json); + } + + // This test is to simulate PATCH requests + @Test + public void testGetJsonFromVertexWithModifyVertex() throws AAIFormatVertexException, AAIException { + if (patchVertex1 == null) + assertTrue("The vertex 1 used for this test is null. Fail immediately.", false); + if (patchVertex2 == null) + assertTrue("The vertex 2 used for this test is null. Fail immediately.", false); + + // Differing Source of Truths will indicate that the action performed modified the vertex + JsonObject json1 = resourceWithSoT.getJsonFromVertex(patchVertex1).get(); + assertEquals(jsonPatchObj1, json1); + + // Timestamps that have a large span in time difference will (likely) indicate that the transaction was not a create (thus, modify) + JsonObject json2 = resourceWithSoT.getJsonFromVertex(patchVertex2).get(); + assertEquals(jsonPatchObj2, json2); + } + + @Test + public void testGetJsonFromVertexWithNullVertex() throws AAIFormatVertexException, AAIException { + // Null check, will return null. + assertNull(resourceWithSoT.getJsonFromVertex(null)); + } + + public void createLoaderEngineSetup() throws AAIException { + + if (loader == null) { + loader = loaderFactory.createLoaderForVersion(factoryType, version); + //loader = LoaderFactory.createLoaderForVersion(factoryType, version); + dbEngine = spy(new JanusGraphDBEngine(QueryStyle.TRAVERSAL, DBConnectionType.CACHED, loader)); + serializer = new DBSerializer(version, dbEngine, factoryType, "Junit"); + resourceWithSoT = new ResourceWithSoT.Builder(loader, serializer, urlBuilder).build(); + + TransactionalGraphEngine.Admin spyAdmin = spy(dbEngine.asAdmin()); + + when(dbEngine.tx()).thenReturn(graph); + when(dbEngine.asAdmin()).thenReturn(spyAdmin); + + when(spyAdmin.getReadOnlyTraversalSource()) + .thenReturn(graph.traversal(GraphTraversalSource.build().with(ReadOnlyStrategy.instance()))); + when(spyAdmin.getTraversalSource()).thenReturn(graph.traversal()); + } + } +} -- cgit 1.2.3-korg