Skip to content

Commit bff8bfd

Browse files
authored
[#538] Encryption of relation records (#567)
Signed-off-by: Aaron Gary <agary@cpointe-inc.com>
1 parent 7e6706d commit bff8bfd

File tree

12 files changed

+228
-7
lines changed

12 files changed

+228
-7
lines changed

foundation/foundation-mda/pom.xml

+6
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,12 @@
170170
<groupId>net.masterthought</groupId>
171171
<artifactId>cucumber-reporting</artifactId>
172172
</dependency>
173+
<dependency>
174+
<groupId>com.boozallen.aissemble</groupId>
175+
<artifactId>foundation-encryption-policy-java</artifactId>
176+
<version>${version.aissemble}</version>
177+
</dependency>
178+
173179
</dependencies>
174180

175181
</project>

foundation/foundation-mda/src/main/java/com/boozallen/aiops/mda/metamodel/element/BaseRecordDecorator.java

+9
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
*/
1212

1313
import com.boozallen.aiops.mda.generator.util.PipelineUtils;
14+
import com.boozallen.aiops.mda.metamodel.AissembleModelInstanceRepository;
1415
import com.fasterxml.jackson.core.JsonProcessingException;
1516
import com.fasterxml.jackson.databind.ObjectMapper;
1617
import org.apache.commons.lang3.StringUtils;
@@ -106,6 +107,14 @@ public List<RecordField> getFields() {
106107
return wrapped.getFields();
107108
}
108109

110+
/**
111+
* {@inheritDoc}
112+
*/
113+
@Override
114+
public List<String> getFieldIds(AissembleModelInstanceRepository metadataRepo) {
115+
return wrapped.getFieldIds(metadataRepo);
116+
}
117+
109118
/**
110119
* {@inheritDoc}
111120
*/

foundation/foundation-mda/src/main/java/com/boozallen/aiops/mda/metamodel/element/BaseStepDecorator.java

+17
Original file line numberDiff line numberDiff line change
@@ -440,4 +440,21 @@ public List<FileStore> getDecoratedFileStores() {
440440
public boolean isGeneric() {
441441
return "generic".equalsIgnoreCase(getType());
442442
}
443+
444+
/**
445+
* Whether or not the step has an inbound native collection type defined
446+
* and also contains relations.
447+
*
448+
* @return true if the step has an inbound native collection type defined and has relations.
449+
*/
450+
public boolean hasInboundNativeCollectionTypeAndRelations() {
451+
boolean isNative = getInbound() != null && getInbound().getNativeCollectionType() != null;
452+
boolean hasRelations = false;
453+
if(getInbound() != null && getInbound().getRecordType() != null) {
454+
Record thisRecord = getInbound().getRecordType().getRecordType();
455+
hasRelations = thisRecord.getRelations() != null && !thisRecord.getRelations().isEmpty();
456+
}
457+
458+
return isNative && hasRelations;
459+
}
443460
}

foundation/foundation-mda/src/main/java/com/boozallen/aiops/mda/metamodel/element/Record.java

+11
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
* #L%
1111
*/
1212

13+
import com.boozallen.aiops.mda.metamodel.AissembleModelInstanceRepository;
1314
import org.technologybrewery.fermenter.mda.metamodel.element.NamespacedMetamodel;
1415

1516
import java.util.Collection;
@@ -49,6 +50,16 @@ public interface Record extends NamespacedMetamodel {
4950
*/
5051
List<RecordField> getFields();
5152

53+
54+
/**
55+
* Returns the fields ids contained in this field container, including field ids from related records.
56+
*
57+
* @param metadataRepo a configured MetadataRepo
58+
* @return a list of field ids
59+
*/
60+
List<String> getFieldIds(AissembleModelInstanceRepository metadataRepo);
61+
62+
5263
/**
5364
* Returns the list of supported Frameworks (e.g. PySpark).
5465
* @return list of supported Frameworks.

foundation/foundation-mda/src/main/java/com/boozallen/aiops/mda/metamodel/element/RecordElement.java

+45
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
*/
1212

1313
import com.boozallen.aiops.mda.ManualActionNotificationService;
14+
import com.boozallen.aiops.mda.metamodel.AissembleModelInstanceRepository;
1415
import com.fasterxml.jackson.annotation.JsonIgnore;
1516
import com.fasterxml.jackson.annotation.JsonInclude;
1617
import com.fasterxml.jackson.annotation.JsonInclude.Include;
@@ -20,6 +21,7 @@
2021

2122
import java.util.ArrayList;
2223
import java.util.List;
24+
import java.util.stream.Collectors;
2325

2426
/**
2527
* Represents a record instance.
@@ -91,6 +93,49 @@ public List<RecordField> getFields() {
9193
return fields;
9294
}
9395

96+
/**
97+
* {@inheritDoc}
98+
*/
99+
@Override
100+
public List<String> getFieldIds(AissembleModelInstanceRepository metadataRepo) {
101+
List<String> fieldIds = new ArrayList<>();
102+
for(RecordField recordField: this.getFields()) {
103+
fieldIds.add(recordField.getName());
104+
}
105+
106+
List<String> relatedFieldIds = getFieldIdsFromRelations(metadataRepo);
107+
fieldIds.addAll(relatedFieldIds);
108+
109+
return fieldIds;
110+
}
111+
112+
/***
113+
* This method gets the fully qualified field ids from relations (dot notation).
114+
* For example: FieldBParent.FieldB
115+
* @param metadataRepo the configured metadataRepop
116+
* @return a list of fully qualified field names
117+
*/
118+
private List<String> getFieldIdsFromRelations(AissembleModelInstanceRepository metadataRepo) {
119+
List<String> fieldIds = new ArrayList<>();
120+
List<Relation> relations = getRelations();
121+
for(Relation relation: relations) {
122+
String relationPackage = relation.getPackage();
123+
String relationName = relation.getName();
124+
Record relatedRecord = metadataRepo.getRecord(relationPackage, relationName);
125+
126+
if(relatedRecord != null) {
127+
128+
List<String> updatedList = relatedRecord.getFieldIds(metadataRepo).stream()
129+
.map(s -> relation.getColumn() + "." + s) // Prepend the prefix
130+
.collect(Collectors.toList());
131+
132+
fieldIds.addAll(updatedList);
133+
}
134+
}
135+
136+
return fieldIds;
137+
}
138+
94139
/**
95140
* Adds a field to this instance.
96141
*

foundation/foundation-mda/src/main/java/com/boozallen/aiops/mda/metamodel/element/RelationElement.java

+9
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,15 @@ public void setDocumentation(String documentation) {
9292
this.documentation = documentation;
9393
}
9494

95+
/***
96+
* Sets the Column name.
97+
* @param column the column name
98+
*/
99+
public void setColumn(String column) {
100+
// This method is used in testing. i.e. not normally set from the RelationElement directly
101+
this.column = column;
102+
}
103+
95104
/**
96105
* Sets the multiplicity value.
97106
*

foundation/foundation-mda/src/main/resources/templates/data-delivery-pyspark/encryption.py.vm

+5-1
Original file line numberDiff line numberDiff line change
@@ -23,19 +23,23 @@
2323
policy_manager = DataEncryptionPolicyManager.getInstance()
2424
retrieved_policies = policy_manager.policies
2525

26+
#if (${step.hasInboundNativeCollectionTypeAndRelations()})
27+
if(len(retrieved_policies.items()) > 0):
28+
raise NotImplementedError("Encryption of records that contain relations is not yet supported.")
29+
#else
2630
for key, encrypt_policy in retrieved_policies.items():
2731
# Encryption policies have a property called encryptPhase.
2832
# If that property is missing then we should ignore the policy.
2933
if encrypt_policy.encryptPhase:
3034
if self.step_phase.lower() == encrypt_policy.encryptPhase.lower():
31-
3235
encrypt_fields = encrypt_policy.encryptFields
3336
input_fields = self.get_fields_list(inbound)
3437
field_intersection = list(set(encrypt_fields) & set(input_fields))
3538

3639
return_payload = self.apply_encryption_to_dataset(inbound, field_intersection, encrypt_policy.encryptAlgorithm)
3740
else:
3841
${step.capitalizedName}Base.logger.info('Encryption policy does not apply to this phase: ' + self.step_phase)
42+
#end
3943

4044
return return_payload
4145
#end

foundation/foundation-mda/src/main/resources/templates/data-delivery-spark/encryption.java.vm

+4
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ ${step.encryptionSignature} {
2424
Map<String, EncryptionPolicy> policies = encryptionPolicyManager.getEncryptPolicies();
2525

2626
if(!policies.isEmpty()) {
27+
#if (${step.hasInboundNativeCollectionTypeAndRelations()})
28+
throw new UnsupportedOperationException("Encryption of records that contain relations is not yet supported.");
29+
#else
2730
for(EncryptionPolicy encryptionPolicy: policies.values()) {
2831
if(stepPhase.equalsIgnoreCase(encryptionPolicy.getEncryptPhase())){
2932
List<String> encryptFields = encryptionPolicy.getEncryptFields();
@@ -103,6 +106,7 @@ ${step.encryptionSignature} {
103106
}
104107
}
105108
}
109+
#end
106110
}
107111
}
108112
#if ($step.hasMessagingInbound())

foundation/foundation-mda/src/test/java/com/boozallen/aiops/mda/metamodel/element/PipelineSteps.java

+112
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import com.boozallen.aiops.mda.generator.common.DataFlowStrategy;
1414
import com.boozallen.aiops.mda.generator.common.MachineLearningStrategy;
1515
import com.boozallen.aiops.mda.generator.common.PipelineContext;
16+
import com.boozallen.aiops.mda.metamodel.AissembleModelInstanceRepository;
1617
import com.boozallen.aiops.mda.util.TestMetamodelUtil;
1718
import com.boozallen.aiops.mda.metamodel.json.AissembleMdaJsonUtils;
1819
import io.cucumber.java.After;
@@ -37,10 +38,14 @@
3738
import java.nio.file.Files;
3839
import java.util.ArrayList;
3940
import java.util.List;
41+
import java.util.Map;
4042
import java.util.Random;
4143
import java.util.regex.Matcher;
4244
import java.util.regex.Pattern;
4345
import java.util.stream.Collectors;
46+
import org.technologybrewery.fermenter.mda.metamodel.ModelInstanceRepositoryManager;
47+
import org.technologybrewery.fermenter.mda.metamodel.ModelRepositoryConfiguration;
48+
import org.technologybrewery.fermenter.mda.metamodel.ModelInstanceUrl;
4449

4550
import static org.junit.Assert.assertEquals;
4651
import static org.junit.Assert.assertFalse;
@@ -56,6 +61,7 @@ public class PipelineSteps extends AbstractModelInstanceSteps {
5661
public static final String DO_MODIFY_REGEX = "DO\\s+MODIFY";
5762
public static final String ABSTRACT_DATA_ACTION_IMPL_INHERIT_REGEX = "AbstractDataActionImpl\\(AbstractDataAction\\):";
5863
public static final String ABSTRACT_PIPELINE_STEP_INHERIT_REGEX = "AbstractPipelineStep\\(AbstractDataActionImpl\\)";
64+
public static final String TEST_RECORD_RELATIONS = "test.record.relations";
5965

6066
protected Pipeline pipeline;
6167
protected Pipeline dataFLowPipeline;
@@ -967,4 +973,110 @@ private List<Step> createStepForPySparkRDMSDataFlowPipeline() {
967973
return steps;
968974
}
969975

976+
977+
@Given("a valid data delivery pipeline with native inbound type")
978+
public void a_valid_data_delivery_pipeline_with_native_inbound_type() throws IOException {
979+
RecordElement newRecordB = createNewRecordWithNameAndPackage("RecordB", RELATION_PACKAGE);
980+
saveRecordToFile(newRecordB);
981+
982+
RecordElement nativeRecord = createNewRecordWithNameAndPackage("RecordA", TEST_RECORD_RELATIONS);
983+
984+
// Create relation from RecordA to RecordB
985+
RelationElement recordRelation = new RelationElement();
986+
recordRelation.setName("RecordB");
987+
recordRelation.setPackage(RELATION_PACKAGE);
988+
recordRelation.setColumn("FieldBParent");
989+
recordRelation.setDocumentation("Some Documentation");
990+
recordRelation.setMultiplicity("1-1");
991+
992+
nativeRecord.addRelation(recordRelation);
993+
saveRecordToFile(nativeRecord);
994+
995+
PipelineElement newPipeline = TestMetamodelUtil.createPipelineWithType("nativeDataDelivery", "com.boozallen.aiops.test", "data-flow", "versioned-streaming-spark-java");
996+
997+
// Add steps to pipeline
998+
StepElement step = new StepElement();
999+
step.setName("inbound-step");
1000+
step.setType("synchronous");
1001+
1002+
PersistElement persist = new PersistElement();
1003+
persist.setType("delta-lake");
1004+
step.setPersist(persist);
1005+
1006+
StepDataBindingElement inbound = new StepDataBindingElement();
1007+
inbound.setType("native");
1008+
inbound.setChannelName("unit-test-inbound");
1009+
inbound.setChannelType("topic");
1010+
1011+
// Add a record type to the inbound
1012+
StepDataRecordTypeElement stepDataRecordTypeElement = new StepDataRecordTypeElement();
1013+
stepDataRecordTypeElement.setName("RecordA");
1014+
stepDataRecordTypeElement.setPackage(TEST_RECORD_RELATIONS);
1015+
inbound.setRecordType(stepDataRecordTypeElement);
1016+
step.setInbound(inbound);
1017+
1018+
StepDataBindingElement outbound = new StepDataBindingElement();
1019+
outbound.setType("messaging");
1020+
outbound.setChannelName("unit-test-outbound");
1021+
outbound.setChannelType("queue");
1022+
step.setOutbound(outbound);
1023+
1024+
for (int j = 0; j < RandomUtils.insecure().randomInt(0, 4); j++) {
1025+
ConfigurationItemElement configurationItem = new ConfigurationItemElement();
1026+
configurationItem.setKey(RandomStringUtils.insecure().nextAlphanumeric(3));
1027+
configurationItem.setValue(RandomStringUtils.insecure().nextAlphanumeric(10));
1028+
step.addConfigurationItem(configurationItem);
1029+
}
1030+
1031+
newPipeline.addStep(step);
1032+
1033+
pipelineFile = savePipelineToFile(newPipeline);
1034+
}
1035+
1036+
private RecordElement createNewRecordWithNameAndPackage(String name, String packageName) {
1037+
RecordElement newRecord = new RecordElement();
1038+
if (StringUtils.isNotBlank(name)) {
1039+
newRecord.setName(name);
1040+
}
1041+
1042+
if (StringUtils.isNotBlank(packageName)) {
1043+
newRecord.setPackage(packageName);
1044+
}
1045+
1046+
return newRecord;
1047+
}
1048+
1049+
@Then("the native inbound pipeline can be read with the associated record and relations")
1050+
public void the_native_inbound_pipeline_can_be_read() {
1051+
primeRecordRepo("example");
1052+
1053+
pipeline = JsonUtils.readAndValidateJson(pipelineFile, PipelineElement.class);
1054+
List<? extends Step> mySteps = pipeline.getSteps();
1055+
1056+
for(Step mystep: mySteps) {
1057+
StepDataBinding myInbound = mystep.getInbound();
1058+
StepDataRecordType stepDataRecordType = myInbound.getRecordType();
1059+
Record myRecord = stepDataRecordType.getRecordType();
1060+
1061+
assertTrue("Record Relations was empty.", myRecord.getRelations() != null && !myRecord.getRelations().isEmpty());
1062+
}
1063+
}
1064+
1065+
/***
1066+
* This method loads the metadataRepo for records. The default metadataRepo does
1067+
* not set the correct ModelInstanceUrl, so calling this will prime the repo with the correct path.
1068+
* @param artifactId The name of the project
1069+
*/
1070+
protected void primeRecordRepo(String artifactId) {
1071+
ModelRepositoryConfiguration config = new ModelRepositoryConfiguration();
1072+
config.setArtifactId(artifactId);
1073+
config.setBasePackage(BOOZ_ALLEN_PACKAGE);
1074+
Map<String, ModelInstanceUrl> metadataUrlMap = config.getMetamodelInstanceLocations();
1075+
metadataUrlMap.put(artifactId,
1076+
new ModelInstanceUrl(artifactId, recordsDirectory.getParentFile().toURI().toString()));
1077+
1078+
metadataRepo = new AissembleModelInstanceRepository(config);
1079+
ModelInstanceRepositoryManager.setRepository(metadataRepo);
1080+
metadataRepo.load();
1081+
}
9701082
}

foundation/foundation-mda/src/test/java/com/boozallen/aiops/mda/metamodel/element/RecordSteps.java

+2-4
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,6 @@
3535
import io.cucumber.java.en.Given;
3636
import io.cucumber.java.en.Then;
3737
import io.cucumber.java.en.When;
38-
//import org.apache.commons.lang3.RandomStringUtils;
39-
//import org.technologybrewery.fermenter.mda.metamodel.element.FieldElement;
4038

4139
public class RecordSteps extends AbstractModelInstanceSteps {
4240

@@ -519,11 +517,11 @@ public void aRecordWithARelationThatDoesNotDefineRequired() {
519517

520518
@Then("the relation has the correct default values")
521519
public void theRelationHasTheCorrectDefaultValues() {
522-
Record parentRecord = this.metadataRepo.getRecord(TEST_RECORD_RELATIONS,"ParentRecord");
520+
Record parentRecord = this.metadataRepo.getRecord(TEST_RECORD_RELATIONS, "ParentRecord");
523521
assertNotNull("Parent Record with relation was not created successfully", parentRecord);
524522
assertNotNull("Parent record does not have the appropriate relation",
525523
parentRecord.getRelations());
526-
for(Relation childRelation : parentRecord.getRelations()) {
524+
for (Relation childRelation : parentRecord.getRelations()) {
527525
assertNull("Child relation should not be required", childRelation.isRequired());
528526
assertNull("Child relation should not have a description", childRelation.getDocumentation());
529527
assertNull("Child relation should not have a column", childRelation.getColumn());

foundation/foundation-mda/src/test/resources/specifications/pipeline-model.feature

+7-1
Original file line numberDiff line numberDiff line change
@@ -193,4 +193,10 @@ Feature: Specify pipelines to be generated
193193
Scenario: Model lineage is invalid for a data delivery pipeline
194194
Given an otherwise valid data delivery pipeline with a step that has model lineage defined
195195
When pipelines are read
196-
Then there are validation error messages
196+
Then there are validation error messages
197+
198+
Scenario: Pipeline with native inbound and record relations can be parsed
199+
Given a valid data delivery pipeline with native inbound type
200+
When pipelines are read
201+
Then the native inbound pipeline can be read with the associated record and relations
202+

foundation/foundation-mda/src/test/resources/specifications/record-model.feature

+1-1
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ Feature: Specify record of semantically defined types
203203

204204
Scenario: A record can reference another record as a field
205205
Given record B
206-
Given record A has a relation to record B
206+
And record A has a relation to record B
207207
When records are read
208208
Then the records are successfully created
209209
And you can reference record B from record A

0 commit comments

Comments
 (0)