From 2f6ca57590ce799108654d82f147b1bc2d45c49c Mon Sep 17 00:00:00 2001 From: Michael Seaton Date: Fri, 6 Dec 2024 15:44:43 -0500 Subject: [PATCH] SDK-346, SDK-363, SDK-364 - Improvements to content package loading --- .../openmrs/maven/plugins/BuildDistroIT.java | 24 ++ .../org/openmrs/maven/plugins/DeployIT.java | 21 +- .../org/openmrs/maven/plugins/SetupIT.java | 38 +++ ...ro-content-package-no-namespace.properties | 8 + .../openmrs/maven/plugins/BuildDistro.java | 4 +- .../openmrs/maven/plugins/ServerUpgrader.java | 33 +-- .../java/org/openmrs/maven/plugins/Setup.java | 3 +- .../maven/plugins/utility/ContentHelper.java | 217 ++++++++++++------ .../maven/plugins/utility/SpaInstaller.java | 17 +- .../maven/plugins/ContentHelperTest.java | 69 ++++++ .../plugins/model/BaseSdkProperties.java | 35 +-- .../maven/plugins/model/ContentPackage.java | 24 ++ .../plugins/model/ContentProperties.java | 14 ++ .../plugins/utility/DistributionBuilder.java | 2 +- .../maven/plugins/utility/DistroHelper.java | 5 +- .../plugins/model/DistroPropertiesTest.java | 54 +++-- 16 files changed, 432 insertions(+), 136 deletions(-) create mode 100644 integration-tests/src/test/resources/integration-test/openmrs-distro-content-package-no-namespace.properties create mode 100644 maven-plugin/src/test/java/org/openmrs/maven/plugins/ContentHelperTest.java create mode 100644 sdk-commons/src/main/java/org/openmrs/maven/plugins/model/ContentPackage.java create mode 100644 sdk-commons/src/main/java/org/openmrs/maven/plugins/model/ContentProperties.java diff --git a/integration-tests/src/test/java/org/openmrs/maven/plugins/BuildDistroIT.java b/integration-tests/src/test/java/org/openmrs/maven/plugins/BuildDistroIT.java index 246bf82d..c2a23262 100644 --- a/integration-tests/src/test/java/org/openmrs/maven/plugins/BuildDistroIT.java +++ b/integration-tests/src/test/java/org/openmrs/maven/plugins/BuildDistroIT.java @@ -187,4 +187,28 @@ public void testBuildDistroWithSpaArtifacts() throws Exception { assertFilePresent("target/web/openmrs-distro.properties"); assertSuccess(); } + + @Test + public void testBuildDistroWithContentPackage() throws Exception { + includeDistroPropertiesFile("openmrs-distro-content-package.properties"); + addTaskParam("dir", "target"); + addTaskParam("ignorePeerDependencies", "false"); + executeTask("build-distro"); + assertFilePresent( "target", "web", "openmrs_core", "openmrs.war"); + assertFilePresent("target", "web", "openmrs_config", "conceptclasses", "hiv", "conceptclasses.csv"); + assertFilePresent("target", "web", "openmrs_config", "conceptsources", "hiv", "conceptsources.csv"); + assertFilePresent("target", "web", "openmrs_config", "encountertypes", "hiv", "encountertypes.csv"); + } + + @Test + public void testBuildDistroWithWithContentPackageWithNoNamespace() throws Exception { + includeDistroPropertiesFile("openmrs-distro-content-package-no-namespace.properties"); + addTaskParam("dir", "target"); + addTaskParam("ignorePeerDependencies", "false"); + executeTask("build-distro"); + assertFilePresent( "target", "web", "openmrs_core", "openmrs.war"); + assertFilePresent("target", "web", "openmrs_config", "conceptclasses", "conceptclasses.csv"); + assertFilePresent("target", "web", "openmrs_config", "conceptsources", "conceptsources.csv"); + assertFilePresent("target", "web", "openmrs_config", "encountertypes", "encountertypes.csv"); + } } diff --git a/integration-tests/src/test/java/org/openmrs/maven/plugins/DeployIT.java b/integration-tests/src/test/java/org/openmrs/maven/plugins/DeployIT.java index 64fe2746..6f8cdd88 100644 --- a/integration-tests/src/test/java/org/openmrs/maven/plugins/DeployIT.java +++ b/integration-tests/src/test/java/org/openmrs/maven/plugins/DeployIT.java @@ -185,6 +185,21 @@ public void deploy_shouldUpgradeDistroWithContentPackage() throws Exception { assertLogContains("+ Adds content package hiv 1.0.0"); } + @Test + public void deploy_shouldUpgradeDistroWithContentPackageWithoutNamespace() throws Exception { + testServerId = setupTestServer("referenceapplication:2.2"); + includeDistroPropertiesFile("openmrs-distro-content-package-no-namespace.properties"); + addAnswer(testServerId); + addAnswer("y"); + addAnswer("y"); + executeTask("deploy"); + assertSuccess(); + assertFilePresent(testServerId, "configuration", "conceptclasses", "conceptclasses.csv"); + assertFilePresent(testServerId, "configuration", "conceptsources", "conceptsources.csv"); + assertFilePresent(testServerId, "configuration", "encountertypes", "encountertypes.csv"); + assertLogContains("+ Adds content package hiv 1.0.0"); + } + @Test public void deploy_shouldReplaceConfigurationAndContentIfChanged() throws Exception { testServerId = setupTestServer("referenceapplication:2.2"); @@ -205,7 +220,7 @@ public void deploy_shouldReplaceConfigurationAndContentIfChanged() throws Except assertNotNull(server); assertThat(server.getConfigArtifacts().size(), equalTo(1)); assertThat(server.getConfigArtifacts().get(0).getArtifactId(), equalTo("distro-emr-configuration")); - assertThat(server.getContentArtifacts().size(), equalTo(0)); + assertThat(server.getContentPackages().size(), equalTo(0)); includeDistroPropertiesFile("openmrs-distro-content-package.properties"); addAnswer(testServerId); @@ -226,8 +241,8 @@ public void deploy_shouldReplaceConfigurationAndContentIfChanged() throws Except server = Server.loadServer(testDirectoryPath.resolve(testServerId)); assertNotNull(server); assertThat(server.getConfigArtifacts().size(), equalTo(0)); - assertThat(server.getContentArtifacts().size(), equalTo(1)); - assertThat(server.getContentArtifacts().get(0).getArtifactId(), equalTo("hiv")); + assertThat(server.getContentPackages().size(), equalTo(1)); + assertThat(server.getContentPackages().get(0).getArtifactId(), equalTo("hiv")); } @Test diff --git a/integration-tests/src/test/java/org/openmrs/maven/plugins/SetupIT.java b/integration-tests/src/test/java/org/openmrs/maven/plugins/SetupIT.java index 00660f8c..2952cde5 100644 --- a/integration-tests/src/test/java/org/openmrs/maven/plugins/SetupIT.java +++ b/integration-tests/src/test/java/org/openmrs/maven/plugins/SetupIT.java @@ -489,6 +489,44 @@ public void setup_shouldInstallWithSpaSpecifiedAsMavenArtifacts() throws Excepti assertFileContains("@openmrs/esm-dispensing-app", serverId, "frontend", "importmap.json"); } + @Test + public void setup_shouldInstallWithContentPackage() throws Exception { + includeDistroPropertiesFile("openmrs-distro-content-package.properties"); + addTaskParam("distro", testDirectory.toString() + File.separator + "openmrs-distro.properties"); + addMockDbSettings(); + + String serverId = UUID.randomUUID().toString(); + addAnswer(serverId); + addAnswer("1044"); + addAnswer("8080"); + + executeTask("setup"); + + assertFilePresent( serverId, "openmrs-2.6.9.war"); + assertFilePresent(serverId, "configuration", "conceptclasses", "hiv", "conceptclasses.csv"); + assertFilePresent(serverId, "configuration", "conceptsources", "hiv", "conceptsources.csv"); + assertFilePresent(serverId, "configuration", "encountertypes", "hiv", "encountertypes.csv"); + } + + @Test + public void setup_shouldInstallWithContentPackageWithNoNamespace() throws Exception { + includeDistroPropertiesFile("openmrs-distro-content-package-no-namespace.properties"); + addTaskParam("distro", testDirectory.toString() + File.separator + "openmrs-distro.properties"); + addMockDbSettings(); + + String serverId = UUID.randomUUID().toString(); + addAnswer(serverId); + addAnswer("1044"); + addAnswer("8080"); + + executeTask("setup"); + + assertFilePresent( serverId, "openmrs-2.6.9.war"); + assertFilePresent(serverId, "configuration", "conceptclasses", "conceptclasses.csv"); + assertFilePresent(serverId, "configuration", "conceptsources", "conceptsources.csv"); + assertFilePresent(serverId, "configuration", "encountertypes", "encountertypes.csv"); + } + private String readValueFromPropertyKey(File propertiesFile, String key) throws Exception { InputStream in = new FileInputStream(propertiesFile); diff --git a/integration-tests/src/test/resources/integration-test/openmrs-distro-content-package-no-namespace.properties b/integration-tests/src/test/resources/integration-test/openmrs-distro-content-package-no-namespace.properties new file mode 100644 index 00000000..219e3f54 --- /dev/null +++ b/integration-tests/src/test/resources/integration-test/openmrs-distro-content-package-no-namespace.properties @@ -0,0 +1,8 @@ +name=Content Package Example +version=1.0.0 +war.openmrs=2.6.9 +content.hiv.groupId=org.openmrs.content +content.hiv.type=zip +content.hiv=1.0.0 +content.hiv.namespace= +db.h2.supported=true \ No newline at end of file diff --git a/maven-plugin/src/main/java/org/openmrs/maven/plugins/BuildDistro.java b/maven-plugin/src/main/java/org/openmrs/maven/plugins/BuildDistro.java index bef9b2d3..2acd5e0f 100644 --- a/maven-plugin/src/main/java/org/openmrs/maven/plugins/BuildDistro.java +++ b/maven-plugin/src/main/java/org/openmrs/maven/plugins/BuildDistro.java @@ -298,9 +298,7 @@ private String buildDistro(File targetDirectory, Distribution distribution) thro File configDir = new File(web, SDKConstants.OPENMRS_SERVER_CONFIGURATION); configurationInstaller.installToDirectory(configDir, distroProperties); - - contentHelper.downloadAndMoveContentBackendConfig(web, distroProperties); - + contentHelper.installBackendConfig(distroProperties, configDir); spaInstaller.installFromDistroProperties(web, distroProperties, ignorePeerDependencies, overrideReuseNodeCache); File owasDir = new File(web, "owa"); diff --git a/maven-plugin/src/main/java/org/openmrs/maven/plugins/ServerUpgrader.java b/maven-plugin/src/main/java/org/openmrs/maven/plugins/ServerUpgrader.java index c2f2d0b7..7bb427ad 100644 --- a/maven-plugin/src/main/java/org/openmrs/maven/plugins/ServerUpgrader.java +++ b/maven-plugin/src/main/java/org/openmrs/maven/plugins/ServerUpgrader.java @@ -5,6 +5,7 @@ import org.apache.maven.shared.utils.StringUtils; import org.openmrs.maven.plugins.model.Artifact; import org.openmrs.maven.plugins.model.BaseSdkProperties; +import org.openmrs.maven.plugins.model.ContentPackage; import org.openmrs.maven.plugins.model.Distribution; import org.openmrs.maven.plugins.model.DistroProperties; import org.openmrs.maven.plugins.model.Server; @@ -15,6 +16,7 @@ import java.io.File; import java.io.IOException; import java.nio.file.Path; +import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -120,15 +122,6 @@ public void upgradeToDistro(Server server, Distribution distribution, boolean ig } } - // Upgrade spa - UpgradeDifferential.ArtifactChanges spaArtifactChanges = upgradeDifferential.getSpaArtifactChanges(); - UpgradeDifferential.PropertyChanges spaBuildChanges = upgradeDifferential.getSpaBuildChanges(); - boolean updateSpa = spaArtifactChanges.hasChanges() || spaBuildChanges.hasChanges(); - if (updateSpa) { - parentTask.spaInstaller.installFromDistroProperties(server.getServerDirectory(), distroProperties, ignorePeerDependencies, overrideReuseNodeCache); - server.replaceSpaProperties(distroProperties.getSpaProperties()); - } - // Upgrade config and content UpgradeDifferential.ArtifactChanges configChanges = upgradeDifferential.getConfigChanges(); UpgradeDifferential.ArtifactChanges contentChanges = upgradeDifferential.getContentChanges(); @@ -160,8 +153,7 @@ public void upgradeToDistro(Server server, Distribution distribution, boolean ig } if (contentChanges.hasChanges()) { - parentTask.contentHelper.downloadAndMoveContentBackendConfig(server.getServerDirectory(), distroProperties); - // TODO: Where is the frontend config installation? + parentTask.contentHelper.installBackendConfig(distroProperties, configDir); for (Artifact artifact : contentChanges.getArtifactsToRemove()) { server.removePropertiesForArtifact(BaseSdkProperties.TYPE_CONTENT, artifact); } @@ -171,6 +163,15 @@ public void upgradeToDistro(Server server, Distribution distribution, boolean ig } } + // Upgrade spa if any of the spa artifacts, build properties, or content packages have changes + UpgradeDifferential.ArtifactChanges spaArtifactChanges = upgradeDifferential.getSpaArtifactChanges(); + UpgradeDifferential.PropertyChanges spaBuildChanges = upgradeDifferential.getSpaBuildChanges(); + boolean updateSpa = spaArtifactChanges.hasChanges() || spaBuildChanges.hasChanges() || contentChanges.hasChanges(); + if (updateSpa) { + parentTask.spaInstaller.installFromDistroProperties(server.getServerDirectory(), distroProperties, ignorePeerDependencies, overrideReuseNodeCache); + server.replaceSpaProperties(distroProperties.getSpaProperties()); + } + server.setVersion(distroProperties.getVersion()); server.setName(distroProperties.getName()); if (server.getDistroPropertiesFile().delete()) { @@ -225,8 +226,14 @@ public UpgradeDifferential calculateUpdateDifferential(Server server, Distributi upgradeDifferential.setConfigChanges(new UpgradeDifferential.ArtifactChanges(oldConfig, newConfig)); // Content - List oldContent = server.getContentArtifacts(); - List newContent = distroProperties.getContentArtifacts(); + List oldContent = new ArrayList<>(); + for (ContentPackage contentPackage : server.getContentPackages()) { + oldContent.add(contentPackage.getArtifact()); + } + List newContent = new ArrayList<>(); + for (ContentPackage contentPackage : distroProperties.getContentPackages()) { + newContent.add(contentPackage.getArtifact()); + } upgradeDifferential.setContentChanges(new UpgradeDifferential.ArtifactChanges(oldContent, newContent)); return upgradeDifferential; diff --git a/maven-plugin/src/main/java/org/openmrs/maven/plugins/Setup.java b/maven-plugin/src/main/java/org/openmrs/maven/plugins/Setup.java index 7e6c22d3..22e78efe 100644 --- a/maven-plugin/src/main/java/org/openmrs/maven/plugins/Setup.java +++ b/maven-plugin/src/main/java/org/openmrs/maven/plugins/Setup.java @@ -284,7 +284,8 @@ public void setup(Server server, DistroProperties distroProperties) throws MojoE distroHelper.parseContentProperties(distroProperties); moduleInstaller.installModulesForDistro(server, distroProperties); - contentHelper.downloadAndMoveContentBackendConfig(server.getServerDirectory(), distroProperties); + File configurationDir = new File(server.getServerDirectory(), SDKConstants.OPENMRS_SERVER_CONFIGURATION); + contentHelper.installBackendConfig(distroProperties, configurationDir); if (spaInstaller != null) { spaInstaller.installFromDistroProperties(server.getServerDirectory(), distroProperties, ignorePeerDependencies, overrideReuseNodeCache); diff --git a/maven-plugin/src/main/java/org/openmrs/maven/plugins/utility/ContentHelper.java b/maven-plugin/src/main/java/org/openmrs/maven/plugins/utility/ContentHelper.java index 3f779271..d258baad 100644 --- a/maven-plugin/src/main/java/org/openmrs/maven/plugins/utility/ContentHelper.java +++ b/maven-plugin/src/main/java/org/openmrs/maven/plugins/utility/ContentHelper.java @@ -1,120 +1,187 @@ package org.openmrs.maven.plugins.utility; import org.apache.commons.io.FileUtils; +import org.apache.commons.lang.StringUtils; import org.apache.maven.plugin.MojoExecutionException; import org.openmrs.maven.plugins.model.Artifact; +import org.openmrs.maven.plugins.model.ContentPackage; +import org.openmrs.maven.plugins.model.ContentProperties; import org.openmrs.maven.plugins.model.DistroProperties; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Properties; +import java.util.Set; + +import static org.openmrs.maven.plugins.utility.SDKConstants.CONTENT_PROPERTIES_NAME; /** * This class downloads and moves content backend config to respective configuration folders. */ public class ContentHelper { - public static final String FRONTEND_CONFIG_FOLDER = Paths.get("configs", "frontend_config").toString(); - public static final String BACKEND_CONFIG_FOLDER = Paths.get("configs", "backend_config").toString(); + private final Logger log = LoggerFactory.getLogger(getClass()); - private final ModuleInstaller moduleInstaller; - private final Wizard wizard; + private final MavenEnvironment mavenEnvironment; public ContentHelper(MavenEnvironment mavenEnvironment) { - this.moduleInstaller = new ModuleInstaller(mavenEnvironment); - this.wizard = mavenEnvironment.getWizard(); + this.mavenEnvironment = mavenEnvironment; } - private File unpackArtifact(Artifact contentArtifact) throws MojoExecutionException { - String artifactId = contentArtifact.getArtifactId(); - // create a temporary artifact folder - File sourceDir; - try { - sourceDir = Files.createTempDirectory("openmrs-sdk-" + artifactId).toFile(); - } catch (IOException e) { - throw new MojoExecutionException("Exception while trying to create temporary directory", e); + public ContentProperties getContentProperties(ContentPackage contentPackage) throws MojoExecutionException { + Artifact artifact = contentPackage.getArtifact(); + log.debug("Retrieving content package: " + artifact); + try (TempDirectory tempDirectory = TempDirectory.create(artifact.getArtifactId() + "-content-package")) { + mavenEnvironment.getArtifactHelper().downloadArtifact(artifact, tempDirectory.getFile(), true); + Properties properties = new Properties(); + File contentPropertiesFile = new File(tempDirectory.getFile(), CONTENT_PROPERTIES_NAME); + if (contentPropertiesFile.exists()) { + PropertiesUtils.loadPropertiesFromFile(contentPropertiesFile, properties); + } + else { + log.warn("No " + CONTENT_PROPERTIES_NAME + " found in " + artifact); + } + return new ContentProperties(properties); } - - Runtime.getRuntime().addShutdownHook(new Thread(() -> FileUtils.deleteQuietly(sourceDir))); - - moduleInstaller.installAndUnpackModule(contentArtifact, sourceDir.getAbsolutePath()); - return sourceDir; } - private void moveBackendConfig(Artifact contentArtifact, File targetDir) throws MojoExecutionException { - File sourceDir = unpackArtifact(contentArtifact); - try { - File backendConfigFiles = sourceDir.toPath().resolve(BACKEND_CONFIG_FOLDER).toFile(); - Path targetPath = targetDir.toPath().toAbsolutePath(); - - if (backendConfigFiles.exists()) { - File[] configDirectories = backendConfigFiles.listFiles(File::isDirectory); - if (configDirectories != null) { - for (File config : configDirectories) { - Path destDir = targetPath.resolve(config.getName()).resolve(contentArtifact.getArtifactId()); - Files.createDirectories(destDir); - - // Copy config files to the matching configuration folder - FileUtils.copyDirectory(config, destDir.toFile()); - } + /** + * Returns all content packages defined in the distro properties, in the order in which they should be installed + * If one content package declares another as a dependency within it's content.properties file, then the dependency + * is returned before the dependent package in the list. + * If no definitive order can be established, an exception is thrown + */ + public List getContentPackagesInInstallationOrder(DistroProperties distroProperties) throws MojoExecutionException { + List ret = new ArrayList<>(); + Set alreadyAdded = new HashSet<>(); + Map packages = new LinkedHashMap<>(); + for (ContentPackage contentPackage : distroProperties.getContentPackages()) { + packages.put(contentPackage, getContentProperties(contentPackage)); + } + int packagesRemaining = packages.size(); + while (packagesRemaining > 0) {; + int packagesAtStart = packagesRemaining; + for (ContentPackage contentPackage : packages.keySet()) { + ContentProperties contentProperties = packages.get(contentPackage); + boolean canInstall = !alreadyAdded.contains(contentPackage.getGroupIdAndArtifactId()); + for (ContentPackage dependentPackage : contentProperties.getContentPackages()) { + canInstall = canInstall && alreadyAdded.contains(dependentPackage.getGroupIdAndArtifactId()); + } + if (canInstall) { + alreadyAdded.add(contentPackage.getGroupIdAndArtifactId()); + ret.add(contentPackage); + packagesRemaining--; } } - } catch (IOException e) { - throw new MojoExecutionException("Error copying backend configuration: " + e.getMessage(), e); - } finally { - FileUtils.deleteQuietly(sourceDir); + if (packagesRemaining == packagesAtStart) { + throw new MojoExecutionException("Unable to order content packages due to unresolved dependencies."); + } } + return ret; } - public List extractAndGetAllContentFrontendConfigs(Artifact contentArtifact) throws MojoExecutionException { - File sourceDir = unpackArtifact(contentArtifact); - List configFiles = new ArrayList<>(); - File frontendConfigFiles = sourceDir.toPath().resolve(FRONTEND_CONFIG_FOLDER).toFile(); - - if (frontendConfigFiles.exists() && frontendConfigFiles.isDirectory()) { - File[] files = frontendConfigFiles.listFiles(); - if (files != null) { - for (File file : files) { - if (file.isFile() && file.length() > 5) { - configFiles.add(file); - } - } - } - } else { - throw new MojoExecutionException("Error: Frontend configuration folder not found."); + /** + * This installs the backend configuration for all content packages defined in the distribution + * Installation is done in the order in which they should be installed, with packages that are dependencies of other packages installed first + */ + public void installBackendConfig(DistroProperties distroProperties, File installDir) throws MojoExecutionException { + log.debug("Installing backend configuration for content packages in distribution"); + for (ContentPackage contentPackage : getContentPackagesInInstallationOrder(distroProperties)) { + log.debug("Installing content package " + contentPackage.getGroupIdAndArtifactId()); + installBackendConfig(contentPackage, installDir); } - return configFiles; } - // This method now sets the static moduleInstaller - public void downloadAndMoveContentBackendConfig(File serverDirectory, DistroProperties distroProperties) throws MojoExecutionException { - if (distroProperties != null) { - File targetDir = new File(serverDirectory, SDKConstants.OPENMRS_SERVER_CONFIGURATION); - List contents = distroProperties.getContentArtifacts(); + /** + * The standard content package template places the backend config into a folder within the zip archive at `configuration/backend_configuration` + * However, other initial content packages did not follow this pattern. + * To account for these, this will also attempt to look for backend_config in other directories, if the preferred directories does not exist + */ + public void installBackendConfig(ContentPackage contentPackage, File installDir) throws MojoExecutionException { + log.debug("Installing backend configuration for " + contentPackage + " to " + installDir); + Artifact artifact = contentPackage.getArtifact(); + try (TempDirectory tempDirectory = TempDirectory.create(artifact.getArtifactId() + "-content-package")) { + mavenEnvironment.getArtifactHelper().downloadArtifact(artifact, tempDirectory.getFile(), true); + + // Get the backend directory. If not found, fall back to some alternatives for compatibility with existing artifacts + File backendDir = tempDirectory.getPath().resolve("configuration").resolve("backend_configuration").toFile(); + if (!backendDir.exists() || !backendDir.isDirectory()) { + backendDir = tempDirectory.getPath().resolve("configs").resolve("backend_config").toFile(); + } + if (!backendDir.exists() || !backendDir.isDirectory()) { + backendDir = tempDirectory.getFile(); + } - if (contents != null) { - for (Artifact content : contents) { - wizard.showMessage("Downloading Content: " + content + "\n"); - moveBackendConfig(content, targetDir); + // If a namespace is passed in, then install backend configurations into namespaced subdirectories for each domain + String namespace = contentPackage.getNamespace(); + boolean emptyNamespace = StringUtils.isBlank(namespace) || namespace.equals(".") || namespace.equals("/") || namespace.equals("false"); + if (emptyNamespace) { + log.debug("Copying " + backendDir + " to " + installDir); + FileUtils.copyDirectory(backendDir, installDir); + } + else { + File[] configFiles = backendDir.listFiles(); + if (configFiles != null) { + for (File configFile : configFiles) { + if (configFile.isDirectory()) { + Path namespacedConfigDir = installDir.toPath().resolve(configFile.getName()).resolve(contentPackage.getNamespace()); + Files.createDirectories(namespacedConfigDir); + log.debug("Copying " + configFile + " to " + namespacedConfigDir); + FileUtils.copyDirectory(configFile, namespacedConfigDir.toFile()); + } + else { + log.debug("Copying " + configFile + " to " + installDir); + FileUtils.copyFile(configFile, installDir); + } + } } } } + catch (IOException e) { + throw new MojoExecutionException("Unable to install backend configuration to " + installDir, e); + } } - public List collectFrontendConfigs(DistroProperties distroProperties) throws MojoExecutionException { - List allConfigFiles = new ArrayList<>(); - if (distroProperties != null) { - List contents = distroProperties.getContentArtifacts(); - if (contents != null) { - for (Artifact contentArtifact : contents) { - allConfigFiles.addAll(extractAndGetAllContentFrontendConfigs(contentArtifact)); - } + public List installFrontendConfigs(ContentPackage contentPackage, File installDir) throws MojoExecutionException { + log.debug("Installing frontend configuration for " + contentPackage + " to " + installDir); + Artifact artifact = contentPackage.getArtifact(); + List ret = new ArrayList<>(); + try (TempDirectory tempDirectory = TempDirectory.create(artifact.getArtifactId() + "-content-package")) { + mavenEnvironment.getArtifactHelper().downloadArtifact(artifact, tempDirectory.getFile(), true); + + // Get the frontend directory. If not found, fall back to some alternatives for compatibility with existing artifacts + File frontendDir = tempDirectory.getPath().resolve("configuration").resolve("frontend_configuration").toFile(); + if (!frontendDir.exists() || !frontendDir.isDirectory()) { + frontendDir = tempDirectory.getPath().resolve("configs").resolve("frontend_config").toFile(); } + + // If a frontend directory is found, copy files within it to target directory, in a subdirectory for the current content package + if (frontendDir.exists() && frontendDir.isDirectory()) { + File targetDir = new File(installDir, artifact.getArtifactId()); + Files.createDirectory(targetDir.toPath()); + log.debug("Copying " + frontendDir + " to " + targetDir); + FileUtils.copyDirectory(frontendDir, targetDir); + ret = Arrays.asList(Objects.requireNonNull(targetDir.listFiles())); + } + else { + log.warn("No frontend configuration found in content package"); + } + + return ret; + } + catch (IOException e) { + throw new MojoExecutionException("Unable to install frontend configuration to " + installDir, e); } - return allConfigFiles; } } diff --git a/maven-plugin/src/main/java/org/openmrs/maven/plugins/utility/SpaInstaller.java b/maven-plugin/src/main/java/org/openmrs/maven/plugins/utility/SpaInstaller.java index 07f6b02c..8acbd1a6 100644 --- a/maven-plugin/src/main/java/org/openmrs/maven/plugins/utility/SpaInstaller.java +++ b/maven-plugin/src/main/java/org/openmrs/maven/plugins/utility/SpaInstaller.java @@ -9,6 +9,7 @@ import org.apache.maven.plugin.MojoExecutionException; import org.openmrs.maven.plugins.model.Artifact; import org.openmrs.maven.plugins.model.BaseSdkProperties; +import org.openmrs.maven.plugins.model.ContentPackage; import org.openmrs.maven.plugins.model.DistroProperties; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -16,6 +17,7 @@ import java.io.File; import java.io.IOException; import java.nio.file.Path; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.LinkedHashMap; @@ -46,7 +48,7 @@ public class SpaInstaller { private static final Logger logger = LoggerFactory.getLogger(SpaInstaller.class); public SpaInstaller() {} - + public SpaInstaller(MavenEnvironment mavenEnvironment) { this.nodeHelper = new NodeHelper(mavenEnvironment); this.moduleInstaller = new ModuleInstaller(mavenEnvironment); @@ -142,13 +144,18 @@ public void installFromDistroProperties(File appDataDir, DistroProperties distro // print frontend tool version number nodeHelper.runNpx(String.format("%s --version", program), legacyPeerDeps); - if (distroProperties.getContentArtifacts().isEmpty()) { + if (distroProperties.getContentPackages().isEmpty()) { nodeHelper.runNpx(String.format("%s assemble --target %s --mode config --config %s", program, buildTargetDir, spaConfigFile), legacyPeerDeps); } else { - List configFiles = contentHelper.collectFrontendConfigs(distroProperties); - String assembleCommand = assembleWithFrontendConfig(program, buildTargetDir, configFiles, spaConfigFile); - nodeHelper.runNpx(assembleCommand, legacyPeerDeps); + try (TempDirectory configFileDir = TempDirectory.create("content-frontend-config")) { + List configFiles = new ArrayList<>(); + for (ContentPackage contentPackage : distroProperties.getContentPackages()) { + configFiles.addAll(contentHelper.installFrontendConfigs(contentPackage, configFileDir.getFile())); + } + String assembleCommand = assembleWithFrontendConfig(program, buildTargetDir, configFiles, spaConfigFile); + nodeHelper.runNpx(assembleCommand, legacyPeerDeps); + } } nodeHelper.runNpx( String.format("%s build --target %s --build-config %s", program, buildTargetDir, spaConfigFile), legacyPeerDeps); diff --git a/maven-plugin/src/test/java/org/openmrs/maven/plugins/ContentHelperTest.java b/maven-plugin/src/test/java/org/openmrs/maven/plugins/ContentHelperTest.java new file mode 100644 index 00000000..38a40cc8 --- /dev/null +++ b/maven-plugin/src/test/java/org/openmrs/maven/plugins/ContentHelperTest.java @@ -0,0 +1,69 @@ +package org.openmrs.maven.plugins; + +import org.junit.Test; +import org.openmrs.maven.plugins.model.ContentPackage; +import org.openmrs.maven.plugins.model.ContentProperties; +import org.openmrs.maven.plugins.model.DistroProperties; +import org.openmrs.maven.plugins.utility.ContentHelper; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertNotNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class ContentHelperTest { + + Map contentPackages = new LinkedHashMap<>(); + Map contentProperties = new LinkedHashMap<>(); + + @Test + public void getContentPackagesInInstallationOrder_shouldOrderDependenciesBeforeDependentPackages() throws Exception { + addTestContentPackage("baseVersion"); + addTestContentPackage("districtVersion", "countryVersion"); + addTestContentPackage("facilityVersion", "districtVersion"); + addTestContentPackage("countryVersion", "baseVersion"); + + ContentHelper contentHelper = mock(ContentHelper.class); + when(contentHelper.getContentPackagesInInstallationOrder(any())).thenCallRealMethod(); + when(contentHelper.getContentProperties(any())).thenAnswer(invocation -> { + ContentPackage contentPackage = invocation.getArgument(0, ContentPackage.class); + return contentProperties.get(contentPackage.getArtifactId()); + }); + + Properties p = new Properties(); + p.put("content.facilityVersion", "1.0.0"); + p.put("content.countryVersion", "1.0.0"); + p.put("content.districtVersion", "1.0.0"); + p.put("content.baseVersion", "1.0.0"); + DistroProperties distroProperties = new DistroProperties(p); + + List packages = contentHelper.getContentPackagesInInstallationOrder(distroProperties); + assertNotNull(packages); + assertThat(packages.size(), equalTo(4)); + assertThat(packages.get(0).getArtifactId(), equalTo("baseVersion")); + assertThat(packages.get(1).getArtifactId(), equalTo("countryVersion")); + assertThat(packages.get(2).getArtifactId(), equalTo("districtVersion")); + assertThat(packages.get(3).getArtifactId(), equalTo("facilityVersion")); + } + + void addTestContentPackage(String artifactId, String... dependantPackages) { + ContentPackage contentPackage = new ContentPackage(); + contentPackage.setArtifactId(artifactId); + contentPackage.setGroupId("org.openmrs.content"); + contentPackage.setVersion("1.0.0"); + contentPackage.setType("zip"); + contentPackages.put(artifactId, contentPackage); + Properties properties = new Properties(); + for (String dependantPackage : dependantPackages) { + properties.put("content." + dependantPackage, "1.0.0"); + } + contentProperties.put(artifactId, new ContentProperties(properties)); + } +} diff --git a/sdk-commons/src/main/java/org/openmrs/maven/plugins/model/BaseSdkProperties.java b/sdk-commons/src/main/java/org/openmrs/maven/plugins/model/BaseSdkProperties.java index 779bfa99..5d17a8ad 100644 --- a/sdk-commons/src/main/java/org/openmrs/maven/plugins/model/BaseSdkProperties.java +++ b/sdk-commons/src/main/java/org/openmrs/maven/plugins/model/BaseSdkProperties.java @@ -30,6 +30,8 @@ public abstract class BaseSdkProperties { public static final String TYPE_CONFIG = "config"; public static final String TYPE_ZIP = "zip"; public static final String INCLUDES = "includes"; + public static final String NAMESPACE = "namespace"; + private static final List PROPERTY_NAMES = Arrays.asList(ARTIFACT_ID, GROUP_ID, VERSION, TYPE, INCLUDES, NAMESPACE); public static final List SPA_ARTIFACT_PROPERTIES = Arrays.asList(ARTIFACT_ID, GROUP_ID, VERSION, TYPE, INCLUDES); protected Properties properties; @@ -139,20 +141,23 @@ public List getConfigArtifacts() { return artifactList; } - public List getContentArtifacts() { - List artifacts = new ArrayList<>(); + public List getContentPackages() { + List contentPackages = new ArrayList<>(); for (String key : getAllKeys()) { String artifactType = getArtifactType(key); if (artifactType.equals(TYPE_CONTENT)) { - artifacts.add(new Artifact( - checkIfOverwritten(key, ARTIFACT_ID), - getParam(key), - checkIfOverwritten(key, GROUP_ID), - checkIfOverwritten(key, TYPE) - )); - } + ContentPackage contentPackage = new ContentPackage(); + contentPackage.setGroupId(checkIfOverwritten(key, GROUP_ID)); + contentPackage.setArtifactId(checkIfOverwritten(key, ARTIFACT_ID)); + contentPackage.setVersion(getParam(key)); + contentPackage.setType(checkIfOverwritten(key, TYPE)); + String namespaceKey = key + "." + NAMESPACE; + String namespace = contains(namespaceKey) ? getParam(namespaceKey) : contentPackage.getArtifactId(); + contentPackage.setNamespace(namespace); + contentPackages.add(contentPackage); + } } - return artifacts; + return contentPackages; } public Set getAllKeys() { @@ -173,13 +178,15 @@ public boolean contains(String propertyName) { protected String getArtifactType(String key) { String[] wordsArray = key.split("\\."); - if(!(wordsArray[wordsArray.length-1].equals(TYPE) || wordsArray[wordsArray.length-1].equals(ARTIFACT_ID) || wordsArray[wordsArray.length-1].equals(GROUP_ID))){ - if(key.contains(".")){ + String lastElement = wordsArray[wordsArray.length - 1]; + if (!PROPERTY_NAMES.contains(lastElement)) { + if (key.contains(".")) { return key.substring(0, key.indexOf(".")); - }else { + } else { return ""; } - }else { + } + else { return ""; } } diff --git a/sdk-commons/src/main/java/org/openmrs/maven/plugins/model/ContentPackage.java b/sdk-commons/src/main/java/org/openmrs/maven/plugins/model/ContentPackage.java new file mode 100644 index 00000000..7349bb1b --- /dev/null +++ b/sdk-commons/src/main/java/org/openmrs/maven/plugins/model/ContentPackage.java @@ -0,0 +1,24 @@ +package org.openmrs.maven.plugins.model; + +import lombok.Data; + +/** + * Represents a Content Package as defined within a properties file + */ +@Data +public class ContentPackage { + + private String groupId; + private String artifactId; + private String version; + private String type; + private String namespace; + + public Artifact getArtifact() { + return new Artifact(artifactId, version, groupId, type); + } + + public String getGroupIdAndArtifactId() { + return groupId + ":" + artifactId; + } +} diff --git a/sdk-commons/src/main/java/org/openmrs/maven/plugins/model/ContentProperties.java b/sdk-commons/src/main/java/org/openmrs/maven/plugins/model/ContentProperties.java new file mode 100644 index 00000000..cf415874 --- /dev/null +++ b/sdk-commons/src/main/java/org/openmrs/maven/plugins/model/ContentProperties.java @@ -0,0 +1,14 @@ +package org.openmrs.maven.plugins.model; + +import java.util.Properties; + +/** + * Represents a `content.properties` file that is included within a Content Package + */ +public class ContentProperties extends BaseSdkProperties { + + public ContentProperties(Properties properties) { + this.properties = properties; + } + +} diff --git a/sdk-commons/src/main/java/org/openmrs/maven/plugins/utility/DistributionBuilder.java b/sdk-commons/src/main/java/org/openmrs/maven/plugins/utility/DistributionBuilder.java index 565f2fc4..8e2e04c8 100644 --- a/sdk-commons/src/main/java/org/openmrs/maven/plugins/utility/DistributionBuilder.java +++ b/sdk-commons/src/main/java/org/openmrs/maven/plugins/utility/DistributionBuilder.java @@ -130,7 +130,7 @@ protected void populateRefApp3xProperties(Distribution distribution, Properties } // Add distro artifact as the config artifact if no config or content are included specifically - if (includedProperties.getConfigArtifacts().isEmpty() && includedProperties.getContentArtifacts().isEmpty()) { + if (includedProperties.getConfigArtifacts().isEmpty() && includedProperties.getContentPackages().isEmpty()) { properties.put("config." + distroArtifactId, distroVersion); properties.put("config." + distroArtifactId + ".groupId", distroGroupId); properties.put("config." + distroArtifactId + ".type", TYPE_ZIP); diff --git a/sdk-commons/src/main/java/org/openmrs/maven/plugins/utility/DistroHelper.java b/sdk-commons/src/main/java/org/openmrs/maven/plugins/utility/DistroHelper.java index 3b12eb83..cc265223 100644 --- a/sdk-commons/src/main/java/org/openmrs/maven/plugins/utility/DistroHelper.java +++ b/sdk-commons/src/main/java/org/openmrs/maven/plugins/utility/DistroHelper.java @@ -6,6 +6,7 @@ import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.project.MavenProject; import org.openmrs.maven.plugins.model.Artifact; +import org.openmrs.maven.plugins.model.ContentPackage; import org.openmrs.maven.plugins.model.Distribution; import org.openmrs.maven.plugins.model.DistroProperties; import org.openmrs.maven.plugins.model.PackageJson; @@ -360,8 +361,8 @@ public void downloadContentPackages(File contentPackageZipFile, DistroProperties Properties contentProperties = new Properties(); - for (Artifact artifact : distroProperties.getContentArtifacts()) { - + for (ContentPackage contentPackage : distroProperties.getContentPackages()) { + Artifact artifact = contentPackage.getArtifact(); String zipFileName = artifact.getArtifactId() + "-" + artifact.getVersion() + ".zip"; File zipFile = downloadDistro(contentPackageZipFile, artifact, zipFileName); diff --git a/sdk-commons/src/test/java/org/openmrs/maven/plugins/model/DistroPropertiesTest.java b/sdk-commons/src/test/java/org/openmrs/maven/plugins/model/DistroPropertiesTest.java index 1be399e0..3c382c2f 100644 --- a/sdk-commons/src/test/java/org/openmrs/maven/plugins/model/DistroPropertiesTest.java +++ b/sdk-commons/src/test/java/org/openmrs/maven/plugins/model/DistroPropertiesTest.java @@ -58,18 +58,22 @@ public void shouldGetContentPropertiesWithDefaults() { properties.setProperty("content.hiv", "1.0.0"); properties.setProperty("content.tb", "2.3.4-SNAPSHOT"); DistroProperties distro = new DistroProperties(properties); - List contentArtifacts = distro.getContentArtifacts(); - assertThat(contentArtifacts, hasSize(2)); - Artifact hiv = findArtifactByArtifactId(contentArtifacts, "hiv"); - assertThat(hiv, notNullValue()); - assertThat(hiv, hasVersion("1.0.0")); - assertThat(hiv.getGroupId(), equalTo(Artifact.GROUP_CONTENT)); - assertThat(hiv.getType(), equalTo(Artifact.TYPE_ZIP)); - Artifact tb = findArtifactByArtifactId(contentArtifacts, "tb"); - assertThat(tb, notNullValue()); - assertThat(tb, hasVersion("2.3.4-SNAPSHOT")); - assertThat(tb.getGroupId(), equalTo(Artifact.GROUP_CONTENT)); - assertThat(tb.getType(), equalTo(Artifact.TYPE_ZIP)); + List contentPackages = distro.getContentPackages(); + assertThat(contentPackages, hasSize(2)); + ContentPackage hivPackage = findContentPackageByArtifactId(contentPackages, "hiv"); + assertThat(hivPackage, notNullValue()); + assertThat(hivPackage.getArtifactId(), equalTo("hiv")); + assertThat(hivPackage.getVersion(), equalTo("1.0.0")); + assertThat(hivPackage.getGroupId(), equalTo(Artifact.GROUP_CONTENT)); + assertThat(hivPackage.getType(), equalTo(Artifact.TYPE_ZIP)); + assertThat(hivPackage.getNamespace(), equalTo("hiv")); + ContentPackage tbPackage = findContentPackageByArtifactId(contentPackages, "tb"); + assertThat(tbPackage, notNullValue()); + assertThat(tbPackage.getArtifactId(), equalTo("tb")); + assertThat(tbPackage.getVersion(), equalTo("2.3.4-SNAPSHOT")); + assertThat(tbPackage.getGroupId(), equalTo(Artifact.GROUP_CONTENT)); + assertThat(tbPackage.getType(), equalTo(Artifact.TYPE_ZIP)); + assertThat(tbPackage.getNamespace(), equalTo("tb")); } @Test @@ -78,14 +82,17 @@ public void shouldGetContentPropertiesWithOverrides() { properties.setProperty("content.hiv", "1.0.0"); properties.setProperty("content.hiv.groupId", "org.openmrs.new"); properties.setProperty("content.hiv.type", "gzip"); + properties.setProperty("content.hiv.namespace", ""); DistroProperties distro = new DistroProperties(properties); - List contentArtifacts = distro.getContentArtifacts(); - assertThat(contentArtifacts, hasSize(1)); - Artifact hiv = findArtifactByArtifactId(contentArtifacts, "hiv"); - assertThat(hiv, notNullValue()); - assertThat(hiv, hasVersion("1.0.0")); - assertThat(hiv.getGroupId(), equalTo("org.openmrs.new")); - assertThat(hiv.getType(), equalTo("gzip")); + List contentPackages = distro.getContentPackages(); + assertThat(contentPackages, hasSize(1)); + ContentPackage hivPackage = contentPackages.get(0); + assertThat(hivPackage, notNullValue()); + assertThat(hivPackage.getArtifactId(), equalTo("hiv")); + assertThat(hivPackage.getVersion(), equalTo("1.0.0")); + assertThat(hivPackage.getGroupId(), equalTo("org.openmrs.new")); + assertThat(hivPackage.getType(), equalTo("gzip")); + assertThat(hivPackage.getNamespace(), equalTo("")); } @Test(expected = MojoExecutionException.class) @@ -182,6 +189,15 @@ private static Artifact findArtifactByArtifactId(List artifacts, Strin return null; } + private static ContentPackage findContentPackageByArtifactId(List contentPackages, String artifactId) { + for (ContentPackage contentPackage : contentPackages) { + if (contentPackage.getArtifact().getArtifactId().equals(artifactId)){ + return contentPackage; + } + } + throw new RuntimeException("No package found with artifactId " + artifactId); + } + public static Matcher hasVersion(final String version) { return new FeatureMatcher(is(version), "version", "artifact version") { @Override