--- /dev/null
+/**
+ * ============LICENSE_START==========================================
+ * org.onap.aai
+ * ===================================================================
+ * Copyright © 2017 AT&T Intellectual Property. All rights reserved.
+ * Copyright © 2017 Amdocs
+ * ===================================================================
+ * 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============================================
+ * ECOMP is a trademark and service mark of AT&T Intellectual Property.
+ */
+package org.openecomp.aai.champ.core;
+
+import static org.junit.Assert.assertTrue;
+
+import java.io.IOException;
+import java.util.Set;
+
+import org.junit.Test;
+import org.openecomp.aai.champ.ChampAPI;
+import org.openecomp.aai.champ.ChampGraph;
+import org.openecomp.aai.champ.exceptions.ChampMarshallingException;
+import org.openecomp.aai.champ.exceptions.ChampObjectNotExistsException;
+import org.openecomp.aai.champ.exceptions.ChampSchemaViolationException;
+import org.openecomp.aai.champ.model.ChampConnectionConstraint;
+import org.openecomp.aai.champ.model.ChampConnectionMultiplicity;
+import org.openecomp.aai.champ.model.ChampField;
+import org.openecomp.aai.champ.model.ChampObject;
+import org.openecomp.aai.champ.model.ChampObject.ReservedTypes;
+import org.openecomp.aai.champ.model.ChampObjectConstraint;
+import org.openecomp.aai.champ.model.ChampPartition;
+import org.openecomp.aai.champ.model.ChampPropertyConstraint;
+import org.openecomp.aai.champ.model.ChampRelationship;
+import org.openecomp.aai.champ.model.ChampRelationshipConstraint;
+import org.openecomp.aai.champ.model.ChampSchema;
+import org.openecomp.aai.champ.schema.AlwaysValidChampSchemaEnforcer;
+import org.openecomp.aai.champ.schema.ChampSchemaEnforcer;
+import org.openecomp.aai.champ.schema.DefaultChampSchemaEnforcer;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+public class ChampSchemaTest extends BaseChampAPITest {
+
+ @Test
+ public void runTest() {
+ for (ChampGraph.Type apiType : ChampGraph.Type.values()) {
+ final String graphName = ChampSchemaTest.class.getSimpleName();
+
+ switch (apiType) {
+ case IN_MEMORY:
+ break;
+ case TITAN:
+ cleanUp(graphName);
+ break;
+ default:
+ break;
+ }
+
+ final ChampAPI api = ChampAPI.Factory.newInstance(apiType);
+
+ try {
+ ChampSchemaTest.testChampSchemaCrud(api.getGraph(graphName));
+ } catch (Throwable t) {
+ throw new AssertionError(apiType + " unit test failed", t);
+ }
+
+ api.shutdown();
+ }
+ }
+
+ public static void testChampSchemaCrud(ChampGraph graph) {
+
+ final ChampSchema schema = ChampSchema.create()
+ .withObjectConstraint()
+ .onType("foo")
+ .withPropertyConstraint()
+ .onField("property1")
+ .required()
+ .build()
+ .withPropertyConstraint()
+ .onField("property2")
+ .optional()
+ .build()
+ .build()
+ .withRelationshipConstraint()
+ .onType("bar")
+ .withPropertyConstraint()
+ .onField("at")
+ .ofType(ChampField.Type.STRING)
+ .optional()
+ .build()
+ .withConnectionConstraint()
+ .sourcedFrom("foo")
+ .targetedToAny()
+ .build()
+ .build()
+ .build();
+
+ try {
+ graph.storeSchema(schema);
+ } catch (ChampSchemaViolationException e) {
+ throw new AssertionError(e);
+ }
+
+ final ChampObject emptyFoo = ChampObject.create()
+ .ofType("foo")
+ .withoutKey()
+ .build();
+
+ try {
+ graph.storeObject(emptyFoo);
+ } catch (ChampMarshallingException e1) {
+ throw new AssertionError(e1);
+ } catch (ChampSchemaViolationException e1) {
+ //Expected, since it does not have the required property "property1"
+ } catch (ChampObjectNotExistsException e) {
+ throw new AssertionError(e);
+ }
+
+ final ChampSchema retrievedSchema = graph.retrieveSchema();
+
+ if (!schema.equals(retrievedSchema)) throw new AssertionError("Retrieved schema is not the same as the schema that was previously stored");
+
+ try {
+ graph.updateSchema(new ChampRelationshipConstraint.Builder("bard").build());
+ assertTrue(graph.retrieveSchema().getRelationshipConstraint("bard").isPresent());
+
+ graph.updateSchema(new ChampObjectConstraint.Builder("baz").build());
+ assertTrue(graph.retrieveSchema().getObjectConstraint("baz").isPresent());
+ } catch (ChampSchemaViolationException e) {
+ throw new AssertionError(e);
+ }
+
+ final ChampSchema updatedSchema = graph.retrieveSchema();
+
+ if (!updatedSchema.getObjectConstraint("baz").isPresent()) throw new AssertionError("Updated schema and retrieved, but retrieved schema did not contain updates");
+ if (!updatedSchema.getRelationshipConstraint("bard").isPresent()) throw new AssertionError("Updated schema and retrieved, but retrieved schema did not contain updates");
+
+ try {
+ graph.updateSchema(new ChampObjectConstraint.Builder("foo")
+ .constraint(
+ new ChampPropertyConstraint.Builder(
+ new ChampField.Builder("property2")
+ .build()
+ )
+ .required(false)
+ .build()
+ )
+ .build());
+
+ final ChampObject storedEmptyFoo = graph.storeObject(emptyFoo);
+
+ graph.deleteObject(storedEmptyFoo.getKey().get());
+ } catch (ChampMarshallingException e) {
+ throw new AssertionError(e);
+ } catch (ChampSchemaViolationException e) {
+ throw new AssertionError(e);
+ } catch (ChampObjectNotExistsException e) {
+ throw new AssertionError(e);
+ }
+
+ graph.deleteSchema();
+ assertTrue(graph.retrieveSchema().equals(ChampSchema.emptySchema()));
+ }
+
+ @Test
+ public void testChampSchemaFluentApi() {
+ final ChampSchema schema = ChampSchema.create()
+ .withObjectConstraint()
+ .onType("foo")
+ .withPropertyConstraint()
+ .onField("bar")
+ .ofType(ChampField.Type.STRING)
+ .required()
+ .build()
+ .withPropertyConstraint()
+ .onField("baz")
+ .ofType(ChampField.Type.BOOLEAN)
+ .optional()
+ .build()
+ .build()
+ .withRelationshipConstraint()
+ .onType("eats")
+ .withPropertyConstraint()
+ .onField("at")
+ .ofType(ChampField.Type.STRING)
+ .required()
+ .build()
+ .withPropertyConstraint()
+ .onField("for")
+ .optional()
+ .build()
+ .withConnectionConstraint()
+ .sourcedFrom("foo")
+ .targetedTo("foo")
+ .withMultiplicity(ChampConnectionMultiplicity.ONE)
+ .build()
+ .withConnectionConstraint()
+ .sourcedFrom("bar")
+ .targetedTo("bar")
+ .build()
+ .build()
+ .build();
+
+ assertTrue(schema.getObjectConstraint("foo").get().getType().equals("foo"));
+
+ for (ChampPropertyConstraint propConst : schema.getObjectConstraint("foo").get().getPropertyConstraints()) {
+ if (propConst.getField().getName().equals("bar")) {
+ assertTrue(propConst.getField().getJavaType().equals(String.class));
+ assertTrue(propConst.isRequired());
+ } else if (propConst.getField().getName().equals("baz")) {
+ assertTrue(propConst.getField().getJavaType().equals(Boolean.class));
+ assertTrue(!propConst.isRequired());
+ } else {
+ throw new AssertionError("Unknown property constraint found: " + propConst);
+ }
+ }
+
+ assertTrue(schema.getRelationshipConstraint("eats").get().getType().equals("eats"));
+
+ for (ChampPropertyConstraint propConst : schema.getRelationshipConstraint("eats").get().getPropertyConstraints()) {
+ if (propConst.getField().getName().equals("at")) {
+ assertTrue(propConst.getField().getJavaType().equals(String.class));
+ assertTrue(propConst.isRequired());
+ } else if (propConst.getField().getName().equals("for")) {
+ assertTrue(propConst.getField().getJavaType().equals(String.class));
+ assertTrue(!propConst.isRequired());
+ } else {
+ throw new AssertionError("Unknown property constraint found: " + propConst);
+ }
+ }
+
+ for (ChampConnectionConstraint connConst : schema.getRelationshipConstraint("eats").get().getConnectionConstraints()) {
+ if (connConst.getSourceType().equals("foo")) {
+ assertTrue(connConst.getTargetType().equals("foo"));
+ assertTrue(connConst.getMultiplicity() == ChampConnectionMultiplicity.ONE);
+ } else if (connConst.getSourceType().equals("bar")) {
+ assertTrue(connConst.getTargetType().equals("bar"));
+ assertTrue(connConst.getMultiplicity() == ChampConnectionMultiplicity.MANY);
+ } else {
+ throw new AssertionError("Unknown connection constraint found: " + connConst);
+ }
+ }
+ }
+
+ @Test
+ public void testDefaultChampSchemaEnforcer() {
+
+ final ChampSchemaEnforcer schemaEnforcer = new DefaultChampSchemaEnforcer();
+ final ChampSchema champSchema = ChampSchema.create()
+ .withObjectConstraint()
+ .onType("foo")
+ .withPropertyConstraint()
+ .onField("bar")
+ .ofType(ChampField.Type.STRING)
+ .required()
+ .build()
+ .build()
+ .withRelationshipConstraint()
+ .onType("makes")
+ .withPropertyConstraint()
+ .onField("bar")
+ .required()
+ .build()
+ .withConnectionConstraint()
+ .sourcedFrom("foo")
+ .targetedTo("fiz")
+ .withMultiplicity(ChampConnectionMultiplicity.ONE)
+ .build()
+ .build()
+ .build();
+
+ try {
+ schemaEnforcer.validate(ChampObject.create()
+ .ofType("foo")
+ .withoutKey()
+ .withProperty("bar", "true")
+ .build(),
+ champSchema.getObjectConstraint("foo").get());
+ } catch (ChampSchemaViolationException e) {
+ throw new AssertionError(e);
+ }
+
+ try {
+ schemaEnforcer.validate(ChampObject.create()
+ .ofType("foo")
+ .withoutKey()
+ .build(),
+ champSchema.getObjectConstraint("foo").get());
+ throw new AssertionError("Failed to enforce required property constraint on object");
+ } catch (ChampSchemaViolationException e) {
+ //Expected
+ }
+
+ try {
+ schemaEnforcer.validate(ChampObject.create()
+ .ofType("foo")
+ .withoutKey()
+ .withProperty("bar", true)
+ .build(),
+ champSchema.getObjectConstraint("foo").get());
+ throw new AssertionError("Failed to enforce property type constraint on object");
+ } catch (ChampSchemaViolationException e) {
+ //Expected
+ }
+
+ try {
+ schemaEnforcer.validate(ChampRelationship.create()
+ .ofType("makes")
+ .withoutKey()
+ .withSource()
+ .ofType("foo")
+ .withoutKey()
+ .build()
+ .withTarget()
+ .ofType("fiz")
+ .withoutKey()
+ .build()
+ .withProperty("bar", "true")
+ .build(),
+ champSchema.getRelationshipConstraint("makes").get()
+ );
+ } catch (ChampSchemaViolationException e) {
+ throw new AssertionError(e);
+ }
+
+ try {
+ schemaEnforcer.validate(ChampRelationship.create()
+ .ofType("makes")
+ .withoutKey()
+ .withSource()
+ .ofType("foo")
+ .withoutKey()
+ .build()
+ .withTarget()
+ .ofType("fiz")
+ .withoutKey()
+ .build()
+ .build(),
+ champSchema.getRelationshipConstraint("makes").get()
+ );
+ throw new AssertionError("Failed to enforce required property constraint on relationship");
+ } catch (ChampSchemaViolationException e) {
+ //Expected
+ }
+
+ try {
+ schemaEnforcer.validate(ChampPartition.create()
+ .withObject(
+ ChampObject.create()
+ .ofType("foo")
+ .withoutKey()
+ .withProperty("bar", "true")
+ .build()
+ )
+ .withObject(
+ ChampObject.create()
+ .ofType("fiz")
+ .withoutKey()
+ .build()
+ )
+ .withRelationship(
+ ChampRelationship.create()
+ .ofType("makes")
+ .withoutKey()
+ .withSource()
+ .ofType("foo")
+ .withoutKey()
+ .withProperty("bar", "true")
+ .build()
+ .withTarget()
+ .ofType("fiz")
+ .withoutKey()
+ .build()
+ .withProperty("bar", "true")
+ .build()
+ )
+ .withRelationship(
+ ChampRelationship.create()
+ .ofType("makes")
+ .withoutKey()
+ .withSource()
+ .ofType("fiz")
+ .withoutKey()
+ .build()
+ .withTarget()
+ .ofType("foo")
+ .withoutKey()
+ .withProperty("bar", "true")
+ .build()
+ .withProperty("bar", "true")
+ .build()
+ )
+ .build(),
+ champSchema
+ );
+ } catch (ChampSchemaViolationException e) {
+ throw new AssertionError(e);
+ }
+
+ try {
+ schemaEnforcer.validate(ChampPartition.create()
+ .withObject(
+ ChampObject.create()
+ .ofType("foo")
+ .withoutKey()
+ .withProperty("bar", "true")
+ .build()
+ )
+ .withObject(
+ ChampObject.create()
+ .ofType("fiz")
+ .withoutKey()
+ .build()
+ )
+ .withRelationship(
+ ChampRelationship.create()
+ .ofType("makes")
+ .withoutKey()
+ .withSource()
+ .ofType("foo")
+ .withoutKey()
+ .withProperty("bar", "true")
+ .build()
+ .withTarget()
+ .ofType("fiz")
+ .withoutKey()
+ .build()
+ .withProperty("bar", "true")
+ .build()
+ )
+ .withRelationship(
+ ChampRelationship.create()
+ .ofType("makes")
+ .withoutKey()
+ .withSource()
+ .ofType("foo")
+ .withoutKey()
+ .withProperty("bar", "true")
+ .build()
+ .withTarget()
+ .ofType("fiz")
+ .withoutKey()
+ .build()
+ .withProperty("bar", "true")
+ .build()
+ )
+ .build(),
+ champSchema
+ );
+ throw new AssertionError("Failed to enforce connection constraint on relationship type 'makes'");
+ } catch (ChampSchemaViolationException e) {
+ //Expected
+ }
+ }
+
+ @Test
+ public void testAlwaysValidChampSchemaEnforcer() {
+
+ final ChampSchemaEnforcer schemaEnforcer = new AlwaysValidChampSchemaEnforcer();
+
+ try {
+ schemaEnforcer.validate(ChampObject.create()
+ .ofType("foo")
+ .withoutKey()
+ .withProperty("bar", true)
+ .build(),
+ new ChampObjectConstraint.Builder("foo")
+ .constraint(
+ new ChampPropertyConstraint.Builder(
+ new ChampField.Builder("bar")
+ .type(ChampField.Type.STRING)
+ .build()
+ )
+ .required(true)
+ .build()
+ )
+ .build()
+ );
+
+ schemaEnforcer.validate(ChampRelationship.create()
+ .ofType("foo")
+ .withoutKey()
+ .withSource()
+ .ofType("foo")
+ .withoutKey()
+ .build()
+ .withTarget()
+ .ofType("fiz")
+ .withoutKey()
+ .build()
+ .withProperty("bar", true)
+ .build(),
+ new ChampRelationshipConstraint.Builder("bar")
+ .constraint(
+ new ChampPropertyConstraint.Builder(
+ new ChampField.Builder("bar")
+ .type(ChampField.Type.STRING)
+ .build()
+ )
+ .required(true)
+ .build()
+ )
+ .build()
+ );
+
+ schemaEnforcer.validate(ChampPartition.create()
+ .withObject(
+ ChampObject.create()
+ .ofType("foo")
+ .withoutKey()
+ .withProperty("bar", true)
+ .build()
+ )
+ .withObject(
+ ChampObject.create()
+ .ofType("fiz")
+ .withoutKey()
+ .withProperty("bar", true)
+ .build()
+ )
+ .withRelationship(
+ ChampRelationship.create()
+ .ofType("makes")
+ .withoutKey()
+ .withSource()
+ .ofType("foo")
+ .withoutKey()
+ .build()
+ .withTarget()
+ .ofType("fiz")
+ .withoutKey()
+ .build()
+ .build()
+ )
+ .withRelationship(
+ ChampRelationship.create()
+ .ofType("makes")
+ .withoutKey()
+ .withSource()
+ .ofType("foo")
+ .withoutKey()
+ .build()
+ .withTarget()
+ .ofType("fiz")
+ .withoutKey()
+ .build()
+ .withProperty("bar", true)
+ .build()
+ )
+ .build(),
+ ChampSchema.create()
+ .withObjectConstraint()
+ .onType("foo")
+ .withPropertyConstraint()
+ .onField("bar")
+ .required()
+ .build()
+ .build()
+ .withRelationshipConstraint()
+ .onType("makes")
+ .withPropertyConstraint()
+ .onField("bar")
+ .required()
+ .build()
+ .withConnectionConstraint()
+ .sourcedFrom("foo")
+ .targetedTo("fiz")
+ .withMultiplicity(ChampConnectionMultiplicity.ONE)
+ .build()
+ .build()
+ .withRelationshipConstraint()
+ .onType("uses")
+ .withConnectionConstraint()
+ .sourcedFromAny()
+ .targetedTo("computer")
+ .build()
+ .build()
+ .withRelationshipConstraint()
+ .onType("destroys")
+ .withConnectionConstraint()
+ .sourcedFrom("computer")
+ .targetedToAny()
+ .build()
+ .build()
+ .build()
+
+ );
+ } catch (ChampSchemaViolationException e) {
+ throw new AssertionError(e);
+ }
+ }
+
+ @Test
+ public void testFluentSchemaApi() {
+ final ChampSchema schema = ChampSchema.create()
+ .withObjectConstraint()
+ .onType("a")
+ .withPropertyConstraint()
+ .onField("z")
+ .ofType(ChampField.Type.STRING)
+ .optional()
+ .build()
+ .build()
+ .withObjectConstraint()
+ .onType("b")
+ .withPropertyConstraint()
+ .onField("y")
+ .ofType(ChampField.Type.LONG)
+ .required()
+ .build()
+ .build()
+ .withRelationshipConstraint()
+ .onType("one")
+ .withPropertyConstraint()
+ .onField("nine")
+ .ofType(ChampField.Type.INTEGER)
+ .optional()
+ .build()
+ .withConnectionConstraint()
+ .sourcedFrom("a")
+ .targetedTo("b")
+ .withMultiplicity(ChampConnectionMultiplicity.NONE)
+ .build()
+ .withConnectionConstraint()
+ .sourcedFrom("a")
+ .targetedToAny()
+ .withMultiplicity(ChampConnectionMultiplicity.ONE)
+ .build()
+ .withConnectionConstraint()
+ .sourcedFromAny()
+ .targetedTo("b")
+ .withMultiplicity(ChampConnectionMultiplicity.MANY)
+ .build()
+ .withConnectionConstraint()
+ .sourcedFromAny()
+ .targetedToAny()
+ .withMultiplicity(ChampConnectionMultiplicity.MANY)
+ .build()
+ .build()
+ .build();
+
+ final ChampObjectConstraint aObjConstraint = schema.getObjectConstraint("a").get();
+
+ assertTrue(aObjConstraint.getType().equals("a"));
+
+ final ChampPropertyConstraint zPropertyConstraint = aObjConstraint.getPropertyConstraint("z").get();
+
+ assertTrue(zPropertyConstraint.getField().getName().equals("z"));
+ assertTrue(zPropertyConstraint.getField().getJavaType().equals(String.class));
+ assertTrue(!zPropertyConstraint.isRequired());
+
+ final ChampObjectConstraint bObjConstraint = schema.getObjectConstraint("b").get();
+
+ assertTrue(bObjConstraint.getType().equals("b"));
+
+ final ChampPropertyConstraint yPropertyConstraint = bObjConstraint.getPropertyConstraint("y").get();
+
+ assertTrue(yPropertyConstraint.getField().getName().equals("y"));
+ assertTrue(yPropertyConstraint.getField().getJavaType().equals(Long.class));
+ assertTrue(yPropertyConstraint.isRequired());
+
+ final ChampRelationshipConstraint oneRelConstraint = schema.getRelationshipConstraint("one").get();
+
+ assertTrue(oneRelConstraint.getType().equals("one"));
+
+ final ChampPropertyConstraint ninePropertyConstraint = oneRelConstraint.getPropertyConstraint("nine").get();
+
+ assertTrue(ninePropertyConstraint.getField().getName().equals("nine"));
+ assertTrue(ninePropertyConstraint.getField().getJavaType().equals(Integer.class));
+ assertTrue(!ninePropertyConstraint.isRequired());
+
+ final Set<ChampConnectionConstraint> connectionConstraints = oneRelConstraint.getConnectionConstraints();
+
+ for (ChampConnectionConstraint cc : connectionConstraints) {
+ if (cc.getSourceType().equals("a") && cc.getTargetType().equals("b")) {
+ assertTrue(cc.getMultiplicity() == ChampConnectionMultiplicity.NONE);
+ } else if (cc.getSourceType().equals(ReservedTypes.ANY.toString()) && cc.getTargetType().equals("b")) {
+ assertTrue(cc.getMultiplicity() == ChampConnectionMultiplicity.MANY);
+ } else if (cc.getSourceType().equals(ReservedTypes.ANY.toString()) && cc.getTargetType().equals(ReservedTypes.ANY.toString())) {
+ assertTrue(cc.getMultiplicity() == ChampConnectionMultiplicity.MANY);
+ } else if (cc.getSourceType().equals("a") && cc.getTargetType().equals(ReservedTypes.ANY.toString())) {
+ assertTrue(cc.getMultiplicity() == ChampConnectionMultiplicity.ONE);
+ } else {
+ throw new AssertionError("Found unspecified connection constraint " + cc);
+ }
+ }
+ }
+
+ @Test
+ public void testJacksonObjectMapping() {
+ final ChampSchema schema = ChampSchema.create()
+ .withObjectConstraint()
+ .onType("a")
+ .withPropertyConstraint()
+ .onField("z")
+ .ofType(ChampField.Type.STRING)
+ .optional()
+ .build()
+ .build()
+ .withObjectConstraint()
+ .onType("b")
+ .withPropertyConstraint()
+ .onField("y")
+ .ofType(ChampField.Type.LONG)
+ .required()
+ .build()
+ .build()
+ .withRelationshipConstraint()
+ .onType("one")
+ .withPropertyConstraint()
+ .onField("nine")
+ .ofType(ChampField.Type.INTEGER)
+ .optional()
+ .build()
+ .withConnectionConstraint()
+ .sourcedFrom("a")
+ .targetedTo("b")
+ .withMultiplicity(ChampConnectionMultiplicity.NONE)
+ .build()
+ .withConnectionConstraint()
+ .sourcedFrom("a")
+ .targetedToAny()
+ .withMultiplicity(ChampConnectionMultiplicity.ONE)
+ .build()
+ .withConnectionConstraint()
+ .sourcedFromAny()
+ .targetedTo("b")
+ .withMultiplicity(ChampConnectionMultiplicity.MANY)
+ .build()
+ .withConnectionConstraint()
+ .sourcedFromAny()
+ .targetedToAny()
+ .withMultiplicity(ChampConnectionMultiplicity.MANY)
+ .build()
+ .build()
+ .build();
+
+ final ObjectMapper om = new ObjectMapper();
+
+ try {
+ final byte[] serialized = om.writeValueAsBytes(schema);
+ System.out.println(new String(serialized, "UTF-8"));
+ final ChampSchema deserialized = om.readValue(serialized, ChampSchema.class);
+ assert schema.equals(deserialized);
+ } catch (IOException e) {
+ throw new AssertionError(e);
+ }
+
+ }
+}