diff --git a/src/main/java/org/hopper/edw/datavault/hopgui/file/modelgraph/ModelDialogValidationSupport.java b/src/main/java/org/hopper/edw/datavault/hopgui/file/modelgraph/ModelDialogValidationSupport.java index 8eb77959..6a3d63c7 100644 --- a/src/main/java/org/hopper/edw/datavault/hopgui/file/modelgraph/ModelDialogValidationSupport.java +++ b/src/main/java/org/hopper/edw/datavault/hopgui/file/modelgraph/ModelDialogValidationSupport.java @@ -38,6 +38,7 @@ import org.hopper.edw.datavault.hopgui.file.dimensional.HopDimensionalFileType; import org.hopper.edw.datavault.hopgui.file.vault.HopVaultFileType; import org.hopper.edw.datavault.metadata.DataVaultModel; +import org.hopper.edw.datavault.metadata.ModelConfigurationResolver; import org.hopper.edw.datavault.metadata.businessvault.BusinessVaultModel; import org.hopper.edw.datavault.metadata.dimensional.DimensionalModel; import org.w3c.dom.Document; @@ -264,6 +265,8 @@ private static M cloneModel( if (model == null) { throw new HopException("Cannot clone a null model"); } + IHopMetadataProvider provider = + metadataProvider != null ? metadataProvider : metadataProviderOf(model); try { String xml = XmlHandler.aroundTag(xmlRootTag, XmlMetadataUtil.serializeObjectToXml(model)); Document document = XmlHandler.loadXmlString(xml); @@ -272,8 +275,10 @@ private static M cloneModel( rootNode = document.getDocumentElement(); } M clone = modelFactory.get(); - XmlMetadataUtil.deSerializeFromXml(rootNode, modelClass, clone, metadataProvider); + XmlMetadataUtil.deSerializeFromXml(rootNode, modelClass, clone, provider); preserveFilename(model, clone); + // Named configuration is metadata, not XML; metadataProvider is transient. + ModelConfigurationResolver.attach(clone, provider); return clone; } catch (HopException e) { throw e; @@ -282,6 +287,19 @@ private static M cloneModel( } } + private static IHopMetadataProvider metadataProviderOf(Object model) { + if (model instanceof DataVaultModel dataVaultModel) { + return dataVaultModel.getMetadataProvider(); + } + if (model instanceof BusinessVaultModel businessVaultModel) { + return businessVaultModel.getMetadataProvider(); + } + if (model instanceof DimensionalModel dimensionalModel) { + return dimensionalModel.getMetadataProvider(); + } + return null; + } + private static void preserveFilename(Object source, Object clone) { if (source instanceof DataVaultModel sourceDv && clone instanceof DataVaultModel cloneDv) { cloneDv.setFilename(sourceDv.getFilename()); diff --git a/src/main/java/org/hopper/edw/datavault/metadata/businessvault/BvTableBase.java b/src/main/java/org/hopper/edw/datavault/metadata/businessvault/BvTableBase.java index 73f49d6b..ccac3122 100644 --- a/src/main/java/org/hopper/edw/datavault/metadata/businessvault/BvTableBase.java +++ b/src/main/java/org/hopper/edw/datavault/metadata/businessvault/BvTableBase.java @@ -44,6 +44,7 @@ import org.hopper.edw.datavault.metadata.DvConstraintDdlSupport; import org.hopper.edw.datavault.metadata.DvDdlSupport; import org.hopper.edw.datavault.metadata.ForeignKeySpec; +import org.hopper.edw.datavault.metadata.ModelConfigurationResolver; import org.hopper.edw.datavault.metadata.targettypemapping.TargetTypeMappingSupport; @Getter @@ -128,6 +129,10 @@ public void check( IVariables variables, BusinessVaultModel model, DataVaultModel dataVaultModel) { + // Table-dialog Validate calls this without BusinessVaultModel.check(), which attaches + // the provider so named BusinessVaultConfiguration / DataVaultConfiguration resolve. + ModelConfigurationResolver.attach(model, metadataProvider); + ModelConfigurationResolver.attach(dataVaultModel, metadataProvider); if (Utils.isEmpty(getName())) { remarks.add( new CheckResult( diff --git a/src/test/java/org/hopper/edw/datavault/hopgui/file/modelgraph/ModelDialogValidationSupportTest.java b/src/test/java/org/hopper/edw/datavault/hopgui/file/modelgraph/ModelDialogValidationSupportTest.java index 81279154..e97ed069 100644 --- a/src/test/java/org/hopper/edw/datavault/hopgui/file/modelgraph/ModelDialogValidationSupportTest.java +++ b/src/test/java/org/hopper/edw/datavault/hopgui/file/modelgraph/ModelDialogValidationSupportTest.java @@ -16,26 +16,42 @@ package org.hopper.edw.datavault.hopgui.file.modelgraph; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; +import java.util.ArrayList; import java.util.List; import org.apache.hop.core.HopEnvironment; import org.apache.hop.core.ICheckResult; +import org.apache.hop.core.database.DatabaseMeta; import org.apache.hop.core.exception.HopException; +import org.apache.hop.core.logging.LogChannel; +import org.apache.hop.core.plugins.PluginRegistry; import org.apache.hop.core.variables.Variables; import org.apache.hop.metadata.serializer.memory.MemoryMetadataProvider; +import org.hopper.edw.datavault.metadata.DataVaultConfiguration; import org.hopper.edw.datavault.metadata.DataVaultModel; import org.hopper.edw.datavault.metadata.DvHub; import org.hopper.edw.datavault.metadata.DvModelCheckOptions; +import org.hopper.edw.datavault.metadata.DvSatellite; +import org.hopper.edw.datavault.metadata.DvTableType; import org.hopper.edw.datavault.metadata.IDvTable; +import org.hopper.edw.datavault.metadata.ModelConfigurationResolver; +import org.hopper.edw.datavault.metadata.businessvault.BusinessVaultConfiguration; +import org.hopper.edw.datavault.metadata.businessvault.BusinessVaultModel; +import org.hopper.edw.datavault.metadata.businessvault.BvDerivativeRef; +import org.hopper.edw.datavault.metadata.businessvault.BvScd2Table; +import org.hopper.edw.datavault.metadata.xp.RegisterModelConfigurationMetadataExtensionPoint; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; class ModelDialogValidationSupportTest { @BeforeAll - static void initHop() throws HopException { + static void initHop() throws Exception { HopEnvironment.init(); + new RegisterModelConfigurationMetadataExtensionPoint() + .callExtensionPoint(LogChannel.GENERAL, new Variables(), PluginRegistry.getInstance()); } @Test @@ -126,4 +142,98 @@ void tableDialogValidateChecksOnlySelectedTable() throws HopException { + " tableOnly=" + tableOnly.size()); } + + @Test + void cloneBusinessVaultModelResolvesNamedTargetDatabase() throws Exception { + MemoryMetadataProvider metadata = namedConfigProvider(); + BusinessVaultModel model = new BusinessVaultModel(); + model.setConfigurationName("business-vault"); + model.setFilename("/tmp/customer.hbv"); + ModelConfigurationResolver.attach(model, metadata); + + assertEquals("Vault", model.getConfigurationOrDefault().getTargetDatabase()); + + BusinessVaultModel clone = + ModelDialogValidationSupport.cloneBusinessVaultModel(model, metadata); + + assertEquals("/tmp/customer.hbv", clone.getFilename()); + assertEquals("business-vault", clone.getConfigurationName()); + assertEquals("Vault", clone.getConfigurationOrDefault().getTargetDatabase()); + } + + @Test + void cloneBusinessVaultModelFallsBackToSourceMetadataProvider() throws Exception { + MemoryMetadataProvider metadata = namedConfigProvider(); + BusinessVaultModel model = new BusinessVaultModel(); + model.setConfigurationName("business-vault"); + ModelConfigurationResolver.attach(model, metadata); + + BusinessVaultModel clone = ModelDialogValidationSupport.cloneBusinessVaultModel(model, null); + + assertEquals("Vault", clone.getConfigurationOrDefault().getTargetDatabase()); + } + + @Test + void clonedScd2TableCheckDoesNotReportMissingNamedBvTargetDatabase() throws Exception { + MemoryMetadataProvider metadata = namedConfigProvider(); + + BusinessVaultModel bvModel = new BusinessVaultModel(); + bvModel.setConfigurationName("business-vault"); + BvScd2Table table = new BvScd2Table(); + table.setName("customer_bv"); + table.setTableName("customer_bv"); + table.setFunctionalTimestampField("x_load_ts"); + table.getDerivatives().add(new BvDerivativeRef("sat_customer", DvTableType.SATELLITE)); + bvModel.getTables().add(table); + ModelConfigurationResolver.attach(bvModel, metadata); + + DataVaultModel dvModel = new DataVaultModel(); + dvModel.setConfigurationName("data-vault"); + DvSatellite satellite = new DvSatellite(); + satellite.setName("sat_customer"); + satellite.setTableName("sat_customer"); + dvModel.getTables().add(satellite); + ModelConfigurationResolver.attach(dvModel, metadata); + + BusinessVaultModel draft = + ModelDialogValidationSupport.cloneBusinessVaultModel(bvModel, metadata); + BvScd2Table draftTable = (BvScd2Table) draft.getTables().getFirst(); + List remarks = new ArrayList<>(); + draftTable.check(remarks, metadata, new Variables(), draft, dvModel); + + assertFalse( + remarks.stream() + .anyMatch( + remark -> + remark.getType() == ICheckResult.TYPE_RESULT_ERROR + && remark.getText() != null + && remark.getText().contains("Business Vault target database")), + () -> "Expected named BV target database to resolve on cloned model, got: " + remarks); + assertFalse( + remarks.stream() + .anyMatch( + remark -> + remark.getType() == ICheckResult.TYPE_RESULT_ERROR + && remark.getText() != null + && remark.getText().contains("Data Vault target database")), + () -> "Expected named DV target database to resolve, got: " + remarks); + } + + private static MemoryMetadataProvider namedConfigProvider() throws Exception { + MemoryMetadataProvider metadata = new MemoryMetadataProvider(); + BusinessVaultConfiguration bvConfig = new BusinessVaultConfiguration(); + bvConfig.setName("business-vault"); + bvConfig.setTargetDatabase("Vault"); + metadata.getSerializer(BusinessVaultConfiguration.class).save(bvConfig); + + DataVaultConfiguration dvConfig = new DataVaultConfiguration(); + dvConfig.setName("data-vault"); + dvConfig.setTargetDatabase("Vault"); + metadata.getSerializer(DataVaultConfiguration.class).save(dvConfig); + + DatabaseMeta vault = new DatabaseMeta(); + vault.setName("Vault"); + metadata.getSerializer(DatabaseMeta.class).save(vault); + return metadata; + } }