Fix creation of sub Avro items 90/69790/2
authorliamfallon <liam.fallon@ericsson.com>
Thu, 4 Oct 2018 06:06:41 +0000 (07:06 +0100)
committerliamfallon <liam.fallon@ericsson.com>
Thu, 4 Oct 2018 15:44:57 +0000 (16:44 +0100)
When an Avro object is created, nested sub avro objects
are not created automatically. This change allows
invocation of a method to create sub objects of Avro
objects.

Issue-ID: POLICY-954
Change-Id: Ie510867f2631ba6a8c6f5452c08aef5db67e8997
Signed-off-by: liamfallon <liam.fallon@ericsson.com>
context/context-management/src/main/java/org/onap/policy/apex/context/SchemaHelper.java
context/context-management/src/main/java/org/onap/policy/apex/context/impl/schema/AbstractSchemaHelper.java
context/context-management/src/main/java/org/onap/policy/apex/context/impl/schema/java/JavaSchemaHelper.java
context/context-management/src/test/java/org/onap/policy/apex/context/impl/schema/java/JavaSchemaHelperInstanceCreationTest.java
plugins/plugins-context/plugins-context-schema/plugins-context-schema-avro/src/main/java/org/onap/policy/apex/plugins/context/schema/avro/AvroSchemaHelper.java
plugins/plugins-context/plugins-context-schema/plugins-context-schema-avro/src/test/java/org/onap/policy/apex/plugins/context/schema/avro/TestAvroSchemaMap.java
plugins/plugins-context/plugins-context-schema/plugins-context-schema-avro/src/test/java/org/onap/policy/apex/plugins/context/schema/avro/TestAvroSchemaRecord.java

index 44ec06c..b6d21a2 100644 (file)
@@ -91,6 +91,14 @@ public interface SchemaHelper {
      */
     Object createNewInstance(Object incomingObject);
 
+    /**
+     * Create an object of a sub type of this object.
+     * 
+     * @param subType the sub type definition of this type
+     * @return a new object of the sub type
+     */
+    Object createNewSubInstance(String subType);
+
     /**
      * Unmarshal an object in schema format into a Java object.
      *
index f50eb59..6d98313 100644 (file)
@@ -22,6 +22,7 @@ package org.onap.policy.apex.context.impl.schema;
 
 import java.lang.reflect.Constructor;
 
+import org.apache.commons.lang3.NotImplementedException;
 import org.onap.policy.apex.context.ContextRuntimeException;
 import org.onap.policy.apex.context.SchemaHelper;
 import org.onap.policy.apex.model.basicmodel.concepts.AxArtifactKey;
@@ -169,4 +170,12 @@ public abstract class AbstractSchemaHelper implements SchemaHelper {
             throw new ContextRuntimeException(returnString);
         }
     }
+    
+    /* (non-Javadoc)
+     * @see org.onap.policy.apex.context.SchemaHelper#createNewSubInstance(java.lang.String)
+     */
+    @Override
+    public Object createNewSubInstance(String subType) {
+        throw new NotImplementedException("sub types are not supported on this schema helper");
+    }
 }
index 800a701..14d44ee 100644 (file)
@@ -96,7 +96,6 @@ public class JavaSchemaHelperInstanceCreationTest {
         }
         assertEquals(true, schemaHelper0.createNewInstance("true"));
 
-
         try {
             schemaHelper1.createNewInstance();
             fail("this test should throw an exception here");
@@ -108,5 +107,12 @@ public class JavaSchemaHelperInstanceCreationTest {
 
         assertEquals("", schemaHelper2.createNewInstance());
         assertEquals("true", schemaHelper2.createNewInstance("true"));
+
+        try {
+            schemaHelper1.createNewSubInstance("SomeSubtype");
+            fail("this test should throw an exception here");
+        } catch (final Exception e) {
+            assertEquals("sub types are not supported on this schema helper", e.getMessage());
+        }
     }
 }
index 015d9ea..723aefd 100644 (file)
@@ -25,8 +25,12 @@ import com.google.gson.GsonBuilder;
 import com.google.gson.JsonElement;
 
 import java.io.ByteArrayOutputStream;
+import java.util.LinkedHashSet;
+import java.util.Set;
 
 import org.apache.avro.Schema;
+import org.apache.avro.Schema.Field;
+import org.apache.avro.Schema.Type;
 import org.apache.avro.generic.GenericDatumReader;
 import org.apache.avro.generic.GenericDatumWriter;
 import org.apache.avro.generic.GenericRecord;
@@ -37,6 +41,7 @@ import org.apache.avro.io.JsonDecoder;
 import org.apache.avro.io.JsonEncoder;
 import org.onap.policy.apex.context.ContextRuntimeException;
 import org.onap.policy.apex.context.impl.schema.AbstractSchemaHelper;
+import org.onap.policy.apex.model.basicmodel.concepts.AxArtifactKey;
 import org.onap.policy.apex.model.basicmodel.concepts.AxKey;
 import org.onap.policy.apex.model.contextmodel.concepts.AxContextSchema;
 import org.slf4j.ext.XLogger;
@@ -101,7 +106,7 @@ public class AvroSchemaHelper extends AbstractSchemaHelper {
         // Create a new instance using the Avro object mapper
         final Object newInstance = avroObjectMapper.createNewInstance(avroSchema);
 
-        // If no new instance is created, use default schema handler behavior
+        // If no new instance is created, use default schema handler behaviour
         if (newInstance != null) {
             return newInstance;
         } else {
@@ -129,6 +134,87 @@ public class AvroSchemaHelper extends AbstractSchemaHelper {
         }
     }
 
+    @Override
+    public Object createNewSubInstance(final String subInstanceType) {
+        final Set<String> foundTypes = new LinkedHashSet<>();
+
+        Object subInstance = createNewSubInstance(avroSchema, subInstanceType, foundTypes);
+
+        if (subInstance != null) {
+            return subInstance;
+        } else {
+            final String returnString = getUserKey().getId() + ": the schema \"" + avroSchema.getName()
+                            + "\" does not have a subtype of type \"" + subInstanceType + "\"";
+            LOGGER.warn(returnString);
+            throw new ContextRuntimeException(returnString);
+        }
+    }
+
+    /**
+     * Create an instance of a sub type of this type.
+     * 
+     * @param schema the Avro schema of the the type
+     * @param subInstanceType the sub type
+     * @param foundTypes types we have already found
+     * @return the sub type schema or null if it is not created
+     */
+    private Object createNewSubInstance(Schema schema, String subInstanceType, final Set<String> foundTypes) {
+        // Try Array element types
+        if (Type.ARRAY == schema.getType()) {
+            Object newInstance = instantiateSubInstance(subInstanceType, schema.getElementType(), foundTypes);
+            if (newInstance != null) {
+                return newInstance;
+            }
+        }
+
+        if (Type.MAP == schema.getType()) {
+            Object newInstance = instantiateSubInstance(subInstanceType, schema.getValueType(), foundTypes);
+            if (newInstance != null) {
+                return newInstance;
+            }
+        }
+
+        if (Type.RECORD == schema.getType()) {
+            for (Field field : schema.getFields()) {
+                Object newInstance = instantiateSubInstance(subInstanceType, field.schema(), foundTypes);
+                if (newInstance != null) {
+                    return newInstance;
+                }
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Instantiate a sub instance of a type.
+     * 
+     * @param subInstanceType the type of the sub instance to create
+     * @param subSchema the sub schema we have received
+     * @param foundTypes types we have already found
+     * @return an instance of the type or null if it is the incorrect type
+     */
+    private Object instantiateSubInstance(final String subInstanceType, final Schema subSchema,
+                    final Set<String> foundTypes) {
+        if (subSchema == null) {
+            return null;
+        }
+
+        // Check for recursive use of field names in records, if we have already checked a field name
+        // skip it this time.
+        if (foundTypes.contains(subSchema.getName())) {
+            return null;
+        }
+
+        foundTypes.add(subSchema.getName());
+
+        if (subSchema.getName().equals(subInstanceType)) {
+            return new AvroObjectMapperFactory().get(AxArtifactKey.getNullKey(), subSchema)
+                            .createNewInstance(subSchema);
+        }
+        return createNewSubInstance(subSchema, subInstanceType, foundTypes);
+    }
+
     @Override
     public Object unmarshal(final Object object) {
         // If an object is already in the correct format, just carry on
index 9bc87cf..37069be 100644 (file)
@@ -26,6 +26,7 @@ import java.io.File;
 import java.io.IOException;
 import java.util.HashMap;
 
+import org.apache.avro.generic.GenericRecord;
 import org.apache.avro.util.Utf8;
 import org.junit.After;
 import org.junit.Before;
@@ -46,7 +47,7 @@ import org.onap.policy.common.parameters.ParameterService;
  * The Class TestAvroSchemaMap.
  *
  * @author Liam Fallon (liam.fallon@ericsson.com)
- * @version 
+ * @version
  */
 public class TestAvroSchemaMap {
     private final AxKey testKey = new AxArtifactKey("AvroTest", "0.0.1");
@@ -66,8 +67,8 @@ public class TestAvroSchemaMap {
         ModelService.registerModel(AxContextSchemas.class, schemas);
         longMapSchema = TextFileUtils.getTextFileAsString("src/test/resources/avsc/MapExampleLong.avsc");
         addressMapSchema = TextFileUtils.getTextFileAsString("src/test/resources/avsc/MapExampleAddress.avsc");
-        addressMapSchemaInvalidFields =
-                TextFileUtils.getTextFileAsString("src/test/resources/avsc/MapExampleAddressInvalidFields.avsc");
+        addressMapSchemaInvalidFields = TextFileUtils
+                        .getTextFileAsString("src/test/resources/avsc/MapExampleAddressInvalidFields.avsc");
     }
 
     /**
@@ -79,7 +80,7 @@ public class TestAvroSchemaMap {
         schemaParameters.setName(ContextParameterConstants.SCHEMA_GROUP_NAME);
         schemaParameters.getSchemaHelperParameterMap().put("AVRO", new AvroSchemaHelperParameters());
         ParameterService.register(schemaParameters);
-        
+
     }
 
     /**
@@ -97,8 +98,8 @@ public class TestAvroSchemaMap {
      */
     @Test
     public void testMapInit() throws IOException {
-        final AxContextSchema avroSchema =
-                new AxContextSchema(new AxArtifactKey("AvroRecord", "0.0.1"), "AVRO", addressMapSchema);
+        final AxContextSchema avroSchema = new AxContextSchema(new AxArtifactKey("AvroRecord", "0.0.1"), "AVRO",
+                        addressMapSchema);
 
         schemas.getSchemasMap().put(avroSchema.getKey(), avroSchema);
         final SchemaHelper schemaHelper = new SchemaHelperFactory().createSchemaHelper(testKey, avroSchema.getKey());
@@ -110,7 +111,7 @@ public class TestAvroSchemaMap {
         final HashMap<?, ?> newMapFull = (HashMap<?, ?>) schemaHelper.createNewInstance(inString);
 
         assertEquals("{\"streetaddress\": \"221 B Baker St.\", \"city\": \"London\"}",
-                newMapFull.get(new Utf8("address2")).toString());
+                        newMapFull.get(new Utf8("address2")).toString());
     }
 
     /**
@@ -120,8 +121,8 @@ public class TestAvroSchemaMap {
      */
     @Test
     public void testLongMapUnmarshalMarshal() throws IOException {
-        final AxContextSchema avroSchema =
-                new AxContextSchema(new AxArtifactKey("AvroMap", "0.0.1"), "AVRO", longMapSchema);
+        final AxContextSchema avroSchema = new AxContextSchema(new AxArtifactKey("AvroMap", "0.0.1"), "AVRO",
+                        longMapSchema);
 
         schemas.getSchemasMap().put(avroSchema.getKey(), avroSchema);
         final SchemaHelper schemaHelper = new SchemaHelperFactory().createSchemaHelper(testKey, avroSchema.getKey());
@@ -137,8 +138,8 @@ public class TestAvroSchemaMap {
      */
     @Test
     public void testAddressMapUnmarshalMarshal() throws IOException {
-        final AxContextSchema avroSchema =
-                new AxContextSchema(new AxArtifactKey("AvroMap", "0.0.1"), "AVRO", addressMapSchema);
+        final AxContextSchema avroSchema = new AxContextSchema(new AxArtifactKey("AvroMap", "0.0.1"), "AVRO",
+                        addressMapSchema);
 
         schemas.getSchemasMap().put(avroSchema.getKey(), avroSchema);
         final SchemaHelper schemaHelper = new SchemaHelperFactory().createSchemaHelper(testKey, avroSchema.getKey());
@@ -147,6 +148,23 @@ public class TestAvroSchemaMap {
         testUnmarshalMarshal(schemaHelper, "src/test/resources/data/MapExampleAddressFull.json");
     }
 
+    /**
+     * Test sub record create.
+     *
+     * @throws IOException Signals that an I/O exception has occurred.
+     */
+    @Test
+    public void testSubRecordCreateRecord() throws IOException {
+        final AxContextSchema avroSchema = new AxContextSchema(new AxArtifactKey("AvroMap", "0.0.1"), "AVRO",
+                        addressMapSchema);
+
+        schemas.getSchemasMap().put(avroSchema.getKey(), avroSchema);
+        final SchemaHelper schemaHelper = new SchemaHelperFactory().createSchemaHelper(testKey, avroSchema.getKey());
+
+        GenericRecord subRecord = (GenericRecord) schemaHelper.createNewSubInstance("AddressUSRecord");
+        assertEquals(null, subRecord.get("streetAddress"));
+    }
+
     /**
      * Test address map unmarshal marshal invalid fields.
      *
@@ -154,8 +172,8 @@ public class TestAvroSchemaMap {
      */
     @Test
     public void testAddressMapUnmarshalMarshalInvalidFields() throws IOException {
-        final AxContextSchema avroSchema =
-                new AxContextSchema(new AxArtifactKey("AvroMap", "0.0.1"), "AVRO", addressMapSchemaInvalidFields);
+        final AxContextSchema avroSchema = new AxContextSchema(new AxArtifactKey("AvroMap", "0.0.1"), "AVRO",
+                        addressMapSchemaInvalidFields);
 
         schemas.getSchemasMap().put(avroSchema.getKey(), avroSchema);
         final SchemaHelper schemaHelper = new SchemaHelperFactory().createSchemaHelper(testKey, avroSchema.getKey());
index 6b1d09e..b79a5cd 100644 (file)
@@ -21,6 +21,7 @@
 package org.onap.policy.apex.plugins.context.schema.avro;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
 
 import java.io.IOException;
 
@@ -28,6 +29,7 @@ import org.apache.avro.generic.GenericRecord;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
+import org.onap.policy.apex.context.ContextRuntimeException;
 import org.onap.policy.apex.context.SchemaHelper;
 import org.onap.policy.apex.context.impl.schema.SchemaHelperFactory;
 import org.onap.policy.apex.context.parameters.ContextParameterConstants;
@@ -45,7 +47,7 @@ import org.onap.policy.common.parameters.ParameterService;
  * The Class TestAvroSchemaRecord.
  *
  * @author Liam Fallon (liam.fallon@ericsson.com)
- * @version 
+ * @version
  */
 public class TestAvroSchemaRecord {
     private final AxKey testKey = new AxArtifactKey("AvroTest", "0.0.1");
@@ -67,8 +69,8 @@ public class TestAvroSchemaRecord {
         recordSchema = TextFileUtils.getTextFileAsString("src/test/resources/avsc/RecordExample.avsc");
         recordSchemaVpn = TextFileUtils.getTextFileAsString("src/test/resources/avsc/RecordExampleVPN.avsc");
         recordSchemaVpnReuse = TextFileUtils.getTextFileAsString("src/test/resources/avsc/RecordExampleVPNReuse.avsc");
-        recordSchemaInvalidFields =
-                TextFileUtils.getTextFileAsString("src/test/resources/avsc/RecordExampleInvalidFields.avsc");
+        recordSchemaInvalidFields = TextFileUtils
+                        .getTextFileAsString("src/test/resources/avsc/RecordExampleInvalidFields.avsc");
     }
 
     /**
@@ -80,7 +82,7 @@ public class TestAvroSchemaRecord {
         schemaParameters.setName(ContextParameterConstants.SCHEMA_GROUP_NAME);
         schemaParameters.getSchemaHelperParameterMap().put("AVRO", new AvroSchemaHelperParameters());
         ParameterService.register(schemaParameters);
-        
+
     }
 
     /**
@@ -98,8 +100,8 @@ public class TestAvroSchemaRecord {
      */
     @Test
     public void testRecordInit() throws IOException {
-        final AxContextSchema avroSchema =
-                new AxContextSchema(new AxArtifactKey("AvroRecord", "0.0.1"), "AVRO", recordSchema);
+        final AxContextSchema avroSchema = new AxContextSchema(new AxArtifactKey("AvroRecord", "0.0.1"), "AVRO",
+                        recordSchema);
 
         schemas.getSchemasMap().put(avroSchema.getKey(), avroSchema);
         final SchemaHelper schemaHelper = new SchemaHelperFactory().createSchemaHelper(testKey, avroSchema.getKey());
@@ -119,8 +121,8 @@ public class TestAvroSchemaRecord {
      */
     @Test
     public void testRecordUnmarshalMarshal() throws IOException {
-        final AxContextSchema avroSchema =
-                new AxContextSchema(new AxArtifactKey("AvroRecord", "0.0.1"), "AVRO", recordSchema);
+        final AxContextSchema avroSchema = new AxContextSchema(new AxArtifactKey("AvroRecord", "0.0.1"), "AVRO",
+                        recordSchema);
 
         schemas.getSchemasMap().put(avroSchema.getKey(), avroSchema);
         final SchemaHelper schemaHelper = new SchemaHelperFactory().createSchemaHelper(testKey, avroSchema.getKey());
@@ -129,6 +131,34 @@ public class TestAvroSchemaRecord {
         testUnmarshalMarshal(schemaHelper, "src/test/resources/data/RecordExampleFull.json");
     }
 
+    /**
+     * Test record create.
+     *
+     * @throws IOException Signals that an I/O exception has occurred.
+     */
+    @Test
+    public void testRecordCreateRecord() throws IOException {
+        final AxContextSchema avroSchema = new AxContextSchema(new AxArtifactKey("AvroRecord", "0.0.1"), "AVRO",
+                        recordSchema);
+
+        schemas.getSchemasMap().put(avroSchema.getKey(), avroSchema);
+        final SchemaHelper schemaHelper = new SchemaHelperFactory().createSchemaHelper(testKey, avroSchema.getKey());
+
+        GenericRecord subRecord = (GenericRecord) schemaHelper.createNewSubInstance("AddressUSRecord");
+        assertEquals(null, subRecord.get("streetAddress"));
+
+        subRecord = (GenericRecord) schemaHelper.createNewSubInstance("EmailAddress");
+        assertEquals(null, subRecord.get("address"));
+
+        try {
+            subRecord = (GenericRecord) schemaHelper.createNewSubInstance("IDontExist");
+            fail("test should throw an exception here");
+        } catch (ContextRuntimeException cre) {
+            assertEquals("AvroTest:0.0.1: the schema \"User\" does not have a subtype of type \"IDontExist\"",
+                            cre.getMessage());
+        }
+    }
+
     /**
      * Test record unmarshal marshal invalid.
      *
@@ -136,8 +166,8 @@ public class TestAvroSchemaRecord {
      */
     @Test
     public void testRecordUnmarshalMarshalInvalid() throws IOException {
-        final AxContextSchema avroSchema =
-                new AxContextSchema(new AxArtifactKey("AvroRecord", "0.0.1"), "AVRO", recordSchemaInvalidFields);
+        final AxContextSchema avroSchema = new AxContextSchema(new AxArtifactKey("AvroRecord", "0.0.1"), "AVRO",
+                        recordSchemaInvalidFields);
 
         schemas.getSchemasMap().put(avroSchema.getKey(), avroSchema);
         final SchemaHelper schemaHelper = new SchemaHelperFactory().createSchemaHelper(testKey, avroSchema.getKey());
@@ -152,8 +182,8 @@ public class TestAvroSchemaRecord {
      */
     @Test
     public void testVpnRecordUnmarshalMarshal() throws IOException {
-        final AxContextSchema avroSchema =
-                new AxContextSchema(new AxArtifactKey("AvroRecord", "0.0.1"), "AVRO", recordSchemaVpn);
+        final AxContextSchema avroSchema = new AxContextSchema(new AxArtifactKey("AvroRecord", "0.0.1"), "AVRO",
+                        recordSchemaVpn);
 
         schemas.getSchemasMap().put(avroSchema.getKey(), avroSchema);
         final SchemaHelper schemaHelper = new SchemaHelperFactory().createSchemaHelper(testKey, avroSchema.getKey());
@@ -168,8 +198,8 @@ public class TestAvroSchemaRecord {
      */
     @Test
     public void testVpnRecordReuse() throws IOException {
-        final AxContextSchema avroSchema =
-                new AxContextSchema(new AxArtifactKey("AvroRecord", "0.0.1"), "AVRO", recordSchemaVpnReuse);
+        final AxContextSchema avroSchema = new AxContextSchema(new AxArtifactKey("AvroRecord", "0.0.1"), "AVRO",
+                        recordSchemaVpnReuse);
         schemas.getSchemasMap().put(avroSchema.getKey(), avroSchema);
 
         schemas.getSchemasMap().put(avroSchema.getKey(), avroSchema);