diff --git a/api/src/main/java/org/openmrs/module/Module.java b/api/src/main/java/org/openmrs/module/Module.java index 03f32697a952..b9cdc4509af8 100644 --- a/api/src/main/java/org/openmrs/module/Module.java +++ b/api/src/main/java/org/openmrs/module/Module.java @@ -83,6 +83,8 @@ public final class Module { private Set packagesWithMappedClasses = new HashSet<>(); + private String configVersion; + private Document config = null; private Document sqldiff = null; @@ -116,13 +118,15 @@ public Module(String name) { * @param description * @param version */ - public Module(String name, String moduleId, String packageName, String author, String description, String version) { + public Module(String name, String moduleId, String packageName, String author, String description, String version, + String configVersion) { this.name = name; this.moduleId = moduleId; this.packageName = packageName; this.author = author; this.description = description; this.version = version; + this.configVersion = configVersion; log.debug("Creating module " + name); } @@ -644,7 +648,21 @@ public Document getConfig() { public void setConfig(Document config) { this.config = config; } - + + /** + * @since 2.8.0 + */ + public String getConfigVersion() { + return configVersion; + } + + /** + * @since 2.8.0 + */ + public void setConfigVersion(String configVersion) { + this.configVersion = configVersion; + } + public Document getSqldiff() { return sqldiff; } diff --git a/api/src/main/java/org/openmrs/module/ModuleClassLoader.java b/api/src/main/java/org/openmrs/module/ModuleClassLoader.java index 58e9ea854f0e..756ae26ee390 100644 --- a/api/src/main/java/org/openmrs/module/ModuleClassLoader.java +++ b/api/src/main/java/org/openmrs/module/ModuleClassLoader.java @@ -22,6 +22,9 @@ import java.net.URL; import java.net.URLClassLoader; import java.net.URLStreamHandlerFactory; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Path; import java.nio.file.Paths; import java.security.CodeSource; import java.security.ProtectionDomain; @@ -362,7 +365,7 @@ static boolean shouldResourceBeIncluded(Module module, URL fileUrl, String openm boolean include = true; for (ModuleConditionalResource conditionalResource : module.getConditionalResources()) { - if (fileUrl.getPath().matches(".*" + conditionalResource.getPath() + "$")) { + if (isMatchingConditionalResource(module, fileUrl, conditionalResource)) { //if a resource matches a path of contidionalResource then it must meet all conditions include = false; @@ -405,6 +408,41 @@ static boolean shouldResourceBeIncluded(Module module, URL fileUrl, String openm return include; } + static boolean isMatchingConditionalResource(Module module, URL fileUrl, ModuleConditionalResource conditionalResource) { + FileSystem fileSystem = FileSystems.getDefault(); + if (ModuleUtil.matchRequiredVersions(module.getConfigVersion(), "2.0")) { + return fileSystem.getPathMatcher(String.format("glob:**/%s", preprocessGlobPattern(conditionalResource.getPath()))) + .matches(Paths.get(fileUrl.getPath())); + } + return fileUrl.getPath().matches(".*" + conditionalResource.getPath() + "$"); + } + + private static String preprocessGlobPattern(String globPattern) { + if (globPattern == null || globPattern.isEmpty()) { + return ""; + } + + globPattern = globPattern.replace("\\", "/"); + globPattern = globPattern.replaceAll("//+", "/"); + + // Remove "file:" prefix if present + if (globPattern.startsWith("file:/")) { + globPattern = globPattern.substring(5); + } + + // Remove drive letter if present (e.g., C:/) + if (globPattern.matches("^[a-zA-Z]:/.*")) { + globPattern = globPattern.substring(2); + } + + if (globPattern.startsWith("/")) { + globPattern = globPattern.substring(1); + } + + return globPattern; + } + + /** * Get the library cache folder for the given module. Each module has a different cache folder * to ease cleanup when unloading a module while openmrs is running diff --git a/api/src/main/java/org/openmrs/module/ModuleFileParser.java b/api/src/main/java/org/openmrs/module/ModuleFileParser.java index 7c68c2f5879f..95a0cb8f2186 100644 --- a/api/src/main/java/org/openmrs/module/ModuleFileParser.java +++ b/api/src/main/java/org/openmrs/module/ModuleFileParser.java @@ -85,6 +85,7 @@ public class ModuleFileParser { validConfigVersions.add("1.5"); validConfigVersions.add("1.6"); validConfigVersions.add("1.7"); + validConfigVersions.add("2.0"); } // TODO - remove this field once ModuleFileParser(File), ModuleFileParser(InputStream) are removed. @@ -318,7 +319,7 @@ private Module createModule(Document config, File moduleFile) { String desc = getElementTrimmed(configRoot, "description"); String version = getElementTrimmed(configRoot, "version"); - Module module = new Module(name, moduleId, packageName, author, desc, version); + Module module = new Module(name, moduleId, packageName, author, desc, version, configVersion); module.setActivatorName(getElementTrimmed(configRoot, "activator")); module.setRequireDatabaseVersion(getElementTrimmed(configRoot, "require_database_version")); diff --git a/api/src/main/resources/org/openmrs/module/dtd/config-2.0.dtd b/api/src/main/resources/org/openmrs/module/dtd/config-2.0.dtd new file mode 100644 index 000000000000..1490b38384cd --- /dev/null +++ b/api/src/main/resources/org/openmrs/module/dtd/config-2.0.dtd @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/api/src/test/java/org/openmrs/module/ModuleClassLoaderTest.java b/api/src/test/java/org/openmrs/module/ModuleClassLoaderTest.java index 2eb78b1dfd2a..c63e2b5e7957 100644 --- a/api/src/test/java/org/openmrs/module/ModuleClassLoaderTest.java +++ b/api/src/test/java/org/openmrs/module/ModuleClassLoaderTest.java @@ -11,26 +11,33 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; - +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.net.MalformedURLException; import java.net.URI; +import java.net.URL; +import java.util.Collections; import java.util.HashMap; import java.util.Map; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import org.openmrs.test.jupiter.BaseContextSensitiveTest; public class ModuleClassLoaderTest extends BaseContextSensitiveTest { - Module mockModule; + Module mockModuleV1_0; + Module mockModuleV2_0; Map mockModules; @BeforeEach public void before() { - mockModule = new Module("mockmodule", "mockmodule", "org.openmrs.module.mockmodule", "author", "description", "1.0"); + mockModuleV1_0 = new Module("mockmodule", "mockmodule", "org.openmrs.module.mockmodule", "author", "description", "1.0", "1.0"); + mockModuleV2_0 = new Module("mockmodule", "mockmodule", "org.openmrs.module.mockmodule", "author", "description", "2.0", "2.0"); mockModules = new HashMap<>(); } @@ -45,9 +52,9 @@ public void shouldResourceBeIncluded_shouldReturnTrueIfFileMatchesAndOpenmrsVers resource.setPath("lib/mockmodule-api-1.10.jar"); resource.setOpenmrsPlatformVersion("1.7-1.8,1.10-1.11"); - mockModule.getConditionalResources().add(resource); + mockModuleV1_0.getConditionalResources().add(resource); - boolean result = ModuleClassLoader.shouldResourceBeIncluded(mockModule, URI.create( + boolean result = ModuleClassLoader.shouldResourceBeIncluded(mockModuleV1_0, URI.create( "file://module/mockmodule/lib/mockmodule-api-1.10.jar").toURL(), "1.10.0-SNAPSHOT", mockModules); assertThat(result, is(true)); @@ -64,9 +71,9 @@ public void shouldResourceBeIncluded_shouldReturnFalseIfFileMatchesButOpenmrsVer resource.setPath("lib/mockmodule-api-1.10.jar"); resource.setOpenmrsPlatformVersion("1.7-1.8, 1.10-1.11"); - mockModule.getConditionalResources().add(resource); + mockModuleV1_0.getConditionalResources().add(resource); - boolean result = ModuleClassLoader.shouldResourceBeIncluded(mockModule, URI.create( + boolean result = ModuleClassLoader.shouldResourceBeIncluded(mockModuleV1_0, URI.create( "file://module/mockmodule/lib/mockmodule-api-1.10.jar").toURL(), "1.12.0-SNAPSHOT", mockModules); assertThat(result, is(false)); @@ -83,9 +90,9 @@ public void shouldResourceBeIncluded_shouldReturnTrueIfFileDoesNotMatchAndOpenmr resource.setPath("lib/mockmodule-api.jar"); resource.setOpenmrsPlatformVersion("1.10-1.11"); - mockModule.getConditionalResources().add(resource); + mockModuleV1_0.getConditionalResources().add(resource); - boolean result = ModuleClassLoader.shouldResourceBeIncluded(mockModule, URI.create( + boolean result = ModuleClassLoader.shouldResourceBeIncluded(mockModuleV1_0, URI.create( "file://module/mockmodule/lib/mockmodule-api-1.10.jar").toURL(), "1.9.8-SNAPSHOT", mockModules); assertThat(result, is(true)); @@ -106,11 +113,11 @@ public void shouldResourceBeIncluded_shouldReturnTrueIfFileMatchesAndModuleVersi module.setVersion("3.0-4.0,1.0-2.0"); resource.getModules().add(module); - mockModule.getConditionalResources().add(resource); + mockModuleV1_0.getConditionalResources().add(resource); mockModules.put("module", "1.1"); - boolean result = ModuleClassLoader.shouldResourceBeIncluded(mockModule, URI.create( + boolean result = ModuleClassLoader.shouldResourceBeIncluded(mockModuleV1_0, URI.create( "file://module/mockmodule/lib/mockmodule-api-1.10.jar").toURL(), "1.10.0-SNAPSHOT", mockModules); assertThat(result, is(true)); @@ -131,9 +138,9 @@ public void shouldResourceBeIncluded_shouldReturnTrueIfFileMatchesAndModuleIsMis module.setVersion("!"); resource.getModules().add(module); - mockModule.getConditionalResources().add(resource); + mockModuleV1_0.getConditionalResources().add(resource); - boolean result = ModuleClassLoader.shouldResourceBeIncluded(mockModule, URI.create( + boolean result = ModuleClassLoader.shouldResourceBeIncluded(mockModuleV1_0, URI.create( "file://module/mockmodule/lib/mockmodule-api-1.10.jar").toURL(), "1.10.0-SNAPSHOT", mockModules); assertThat(result, is(true)); @@ -154,12 +161,12 @@ public void shouldResourceBeIncluded_shouldReturnFalseIfFileMatchesAndModuleIsNo module.setVersion("!"); resource.getModules().add(module); - mockModule.getConditionalResources().add(resource); + mockModuleV1_0.getConditionalResources().add(resource); - ModuleFactory.getStartedModulesMap().put("module", new Module("", "module", "", "", "", "3.0")); + ModuleFactory.getStartedModulesMap().put("module", new Module("", "module", "", "", "", "3.0", "1.0")); mockModules.put("module", "3.0"); - boolean result = ModuleClassLoader.shouldResourceBeIncluded(mockModule, URI.create( + boolean result = ModuleClassLoader.shouldResourceBeIncluded(mockModuleV1_0, URI.create( "file://module/mockmodule/lib/mockmodule-api-1.10.jar").toURL(), "1.10.0-SNAPSHOT", mockModules); assertThat(result, is(false)); @@ -182,11 +189,11 @@ public void shouldResourceBeIncluded_shouldReturnFalseIfFileMatchesAndModuleVers module.setVersion("1.0-2.0"); resource.getModules().add(module); - mockModule.getConditionalResources().add(resource); + mockModuleV1_0.getConditionalResources().add(resource); mockModules.put("module", "3.0"); - boolean result = ModuleClassLoader.shouldResourceBeIncluded(mockModule, URI.create( + boolean result = ModuleClassLoader.shouldResourceBeIncluded(mockModuleV1_0, URI.create( "file://module/mockmodule/lib/mockmodule-api-1.10.jar").toURL(), "1.10.0-SNAPSHOT", mockModules); assertThat(result, is(false)); @@ -208,11 +215,11 @@ public void shouldResourceBeIncluded_shouldReturnFalseIfFileMatchesAndOpenmrsVer module.setVersion("1.0-2.0,4.0"); resource.getModules().add(module); - mockModule.getConditionalResources().add(resource); + mockModuleV1_0.getConditionalResources().add(resource); mockModules.put("module", "3.0"); - boolean result = ModuleClassLoader.shouldResourceBeIncluded(mockModule, URI.create( + boolean result = ModuleClassLoader.shouldResourceBeIncluded(mockModuleV1_0, URI.create( "file://module/mockmodule/lib/mockmodule-api-1.10.jar").toURL(), "1.10.0-SNAPSHOT", mockModules); assertThat(result, is(false)); @@ -232,11 +239,11 @@ public void shouldResourceBeIncluded_shouldReturnFalseIfFileMatchesAndModuleNotF module.setVersion("1.0-2.0"); resource.getModules().add(module); - mockModule.getConditionalResources().add(resource); + mockModuleV1_0.getConditionalResources().add(resource); mockModules.put("differentModule", "1.0"); - boolean result = ModuleClassLoader.shouldResourceBeIncluded(mockModule, URI.create( + boolean result = ModuleClassLoader.shouldResourceBeIncluded(mockModuleV1_0, URI.create( "file://module/mockmodule/lib/mockmodule-api-1.10.jar").toURL(), "1.10.0-SNAPSHOT", mockModules); assertThat(result, is(false)); @@ -257,13 +264,232 @@ public void shouldResourceBeIncluded_shouldReturnTrueIfFileDoesNotMatchAndModule module.setVersion("1.0-2.0"); resource.getModules().add(module); - mockModule.getConditionalResources().add(resource); + mockModuleV1_0.getConditionalResources().add(resource); mockModules.put("module", "3.0"); - boolean result = ModuleClassLoader.shouldResourceBeIncluded(mockModule, URI.create( + boolean result = ModuleClassLoader.shouldResourceBeIncluded(mockModuleV1_0, URI.create( "file://module/mockmodule/lib/mockmodule-api-1.10.jar").toURL(), "1.10.0-SNAPSHOT", mockModules); assertThat(result, is(true)); } + + /** + * @throws MalformedURLException + * @see ModuleClassLoader#shouldResourceBeIncluded(Module, java.net.URL, String, java.util.Map) + */ + @Test + public void shouldResourceBeIncluded_ShouldNotIncludeWhenVersionWildcardMatchUsingGlobs() throws MalformedURLException { + + final String conditionalResourceModuleId = "module123"; + final String conditionalResourceVersion = "!"; + final String conditionalResourcePath = "/lib/jackson-mapper-asl*"; + final URL fileUrl = URI.create( + "file:/atomfeed/lib/jackson-mapper-asl-1.9.13.jar").toURL(); + final String relatedModuleVersion = "1.2.3"; + + Map startedRelatedModules = new HashMap<>(); + startedRelatedModules.put(conditionalResourceModuleId, relatedModuleVersion); + + ModuleConditionalResource.ModuleAndVersion moduleIdAndVersion = new ModuleConditionalResource.ModuleAndVersion(); + moduleIdAndVersion.setModuleId(conditionalResourceModuleId); + moduleIdAndVersion.setVersion(conditionalResourceVersion); + + ModuleConditionalResource resource = new ModuleConditionalResource(); + resource.setPath(conditionalResourcePath); + resource.setOpenmrsPlatformVersion("1.7-1.8,1.10-1.11"); + resource.setModules(Collections.singletonList(moduleIdAndVersion)); + mockModuleV2_0.getConditionalResources().add(resource); + + ModuleFactory.getStartedModulesMap().put(conditionalResourceModuleId, new Module("", conditionalResourceModuleId, "", "", "", "3.0", "1.0")); + + boolean result = ModuleClassLoader.shouldResourceBeIncluded(mockModuleV2_0, fileUrl, "1.10.0-SNAPSHOT", startedRelatedModules); + + assertThat(result, is(false)); + + ModuleFactory.getStartedModulesMap().clear(); + } + + @Test + public void shouldResourceBeIncluded_ShouldIncludeMatchingPlatformVersionsUsingGlobs() + throws MalformedURLException { + + final String conditionalResourceModuleId = "module123"; + final String commonModuleVersion = "1.2.3"; + final String commonPlatformVersion = "1.6.0"; + final String conditionalResourcePath = "/lib/jackson-mapper-asl*"; + final URL fileUrl = URI.create( + "file:/atomfeed/lib/jackson-mapper-asl-1.9.13.jar").toURL(); + + Map startedRelatedModules = new HashMap<>(); + startedRelatedModules.put(conditionalResourceModuleId, commonModuleVersion); + + ModuleConditionalResource.ModuleAndVersion moduleIdAndVersion = new ModuleConditionalResource.ModuleAndVersion(); + moduleIdAndVersion.setModuleId(conditionalResourceModuleId); + moduleIdAndVersion.setVersion(commonModuleVersion); + + ModuleConditionalResource resource = new ModuleConditionalResource(); + resource.setPath(conditionalResourcePath); + resource.setOpenmrsPlatformVersion(commonPlatformVersion); + resource.setModules(Collections.singletonList(moduleIdAndVersion)); + mockModuleV2_0.getConditionalResources().add(resource); + + ModuleFactory.getStartedModulesMap().put(conditionalResourceModuleId, new Module("", conditionalResourceModuleId, "", "", "", commonModuleVersion, "1.0")); + + boolean result = ModuleClassLoader.shouldResourceBeIncluded(mockModuleV2_0, fileUrl, commonPlatformVersion, startedRelatedModules); + + assertThat(result, is(true)); + + ModuleFactory.getStartedModulesMap().clear(); + } + + @Test + public void shouldResourceBeIncluded_ShouldNotIncludeModuleWhenVersionsMatchUsingGlobs() + throws MalformedURLException { + + final String conditionalResourceModuleId = "module123"; + final String commonPlatformVersion = "1.2.3"; + final String conditionalResourceModuleVersion = "1.2.*"; + final String otherModuleVersion = "2.2.2"; + final String conditionalResourcePath = "/lib/jackson-mapper-asl*"; + final URL fileUrl = URI.create( + "file:/atomfeed/lib/jackson-mapper-asl-1.9.13.jar").toURL(); + + Map startedRelatedModules = new HashMap<>(); + startedRelatedModules.put(conditionalResourceModuleId, otherModuleVersion); + + ModuleConditionalResource.ModuleAndVersion moduleIdAndVersion = new ModuleConditionalResource.ModuleAndVersion(); + moduleIdAndVersion.setModuleId(conditionalResourceModuleId); + moduleIdAndVersion.setVersion(conditionalResourceModuleVersion); + + ModuleConditionalResource resource = new ModuleConditionalResource(); + resource.setPath(conditionalResourcePath); + resource.setOpenmrsPlatformVersion(commonPlatformVersion); + resource.setModules(Collections.singletonList(moduleIdAndVersion)); + mockModuleV2_0.getConditionalResources().add(resource); + + ModuleFactory.getStartedModulesMap().put(conditionalResourceModuleId, new Module("", conditionalResourceModuleId, "", "", "", otherModuleVersion, "1.0")); + + boolean result = ModuleClassLoader.shouldResourceBeIncluded(mockModuleV2_0, fileUrl, commonPlatformVersion, startedRelatedModules); + + assertThat(result, is(false)); + + ModuleFactory.getStartedModulesMap().clear(); + } + + @Test + public void shouldResourceBeIncluded_ShouldNotIncludeWhenPlatformVersionsMisnmatchUsingGlobs() + throws MalformedURLException { + + final String conditionalResourceModuleId = "module123"; + final String conditionalResourceVersion = "1.0.0"; + final String conditionalResourcePath = "/lib/jackson-mapper-asl*"; + final URL fileUrl = URI.create( + "file:/atomfeed/lib/jackson-mapper-asl-1.9.13.jar").toURL(); + + final String conditionalResourcePlatformVersion = "1.0.*"; + final String openmrsPlatformVersion = "2.0.0"; + + Map startedRelatedModules = new HashMap<>(); + startedRelatedModules.put(conditionalResourceModuleId, conditionalResourceVersion); + + ModuleConditionalResource.ModuleAndVersion moduleIdAndVersion = new ModuleConditionalResource.ModuleAndVersion(); + moduleIdAndVersion.setModuleId(conditionalResourceModuleId); + moduleIdAndVersion.setVersion(conditionalResourceVersion); + + ModuleConditionalResource resource = new ModuleConditionalResource(); + resource.setPath(conditionalResourcePath); + resource.setOpenmrsPlatformVersion(conditionalResourcePlatformVersion); + resource.setModules(Collections.singletonList(moduleIdAndVersion)); + mockModuleV2_0.getConditionalResources().add(resource); + + ModuleFactory.getStartedModulesMap().put(conditionalResourceModuleId, new Module("", conditionalResourceModuleId, "", "", "", "3.0", "1.0")); + + boolean result = ModuleClassLoader.shouldResourceBeIncluded(mockModuleV2_0, fileUrl, openmrsPlatformVersion, startedRelatedModules); + + assertThat(result, is(false)); + + ModuleFactory.getStartedModulesMap().clear(); + } + + @ParameterizedTest + @ValueSource(strings = { + "lib/jackson-mapper-asl-1.9.13.jar", + "/lib/jackson-mapper-asl-1.9.13.jar", + "\\lib/jackson-mapper-asl-1.9.13.jar", + "atomfeed/lib/jackson-mapper-asl-1.9.13.jar", + "/atomfeed/lib/jackson-mapper-asl-1.9.13.jar", + "\\atomfeed/lib/jackson-mapper-asl-1.9.13.jar", + "/lib/jackson-mapper-asl-**.jar", + "/lib/jackson-mapper-asl-**", + "/lib/jackson-**-1.9.13.jar", + "atomfeed/*/jackson-mapper-asl-1.9.13.jar", + "C:/atomfeed/lib/jackson-mapper-asl-1.9.13.jar", + "D:\\atomfeed\\lib\\jackson-mapper-asl-1.9.13.jar" + }) + void testGlobPatternMatches(String filePath) throws MalformedURLException { + final String conditionalResourceModuleId = "module123"; + final String conditionalResourceVersion = "1.0.0"; + ModuleConditionalResource.ModuleAndVersion moduleIdAndVersion = new ModuleConditionalResource.ModuleAndVersion(); + moduleIdAndVersion.setModuleId(conditionalResourceModuleId); + moduleIdAndVersion.setVersion(conditionalResourceVersion); + + ModuleConditionalResource conditionalResource = new ModuleConditionalResource(); + conditionalResource.setPath(filePath); + + final URL fileUrl = URI.create( + "file:/atomfeed/lib/jackson-mapper-asl-1.9.13.jar").toURL(); + assertTrue(ModuleClassLoader.isMatchingConditionalResource(mockModuleV2_0, fileUrl, conditionalResource), + "Path should match glob pattern: " + filePath); + } + + @ParameterizedTest + @ValueSource(strings = { + "/libs/jackson-mapper-asl.jar", + "/library/jackson-mapper-asl-1.9.13.jar", + "/otherpath/lib/jackson-mapping-asl-1.9.13.jar", + "/lib/jackson-mapper-xyz.jar", + "/lib/jackson-mapper-asl.txt", + "/lib/jackson-mapper-asl-1.9.13.json", + "/lib/otherfolder/jackson-mapper-asl.jar", + "/lib/jackson-mapper-asl/subdir/jackson-mapper-asl-1.9.13.jar", + "C:/atomfeeds/lib/jackson-mapper-asl.jar", + "D:\\lib\\jackson-mapper-asl-1.9.13.doc", + "/a/b/c/atomfeed/lib/jackson-mapper-asl-1.9.13.jar", + "/a/b/c/atomfeed/lib/jackson-mapper-asl-**.jar", + }) + void testGlobPatternDoesNotMatch(String filePath) throws MalformedURLException { + final String conditionalResourceModuleId = "module123"; + final String conditionalResourceVersion = "1.0.0"; + ModuleConditionalResource.ModuleAndVersion moduleIdAndVersion = new ModuleConditionalResource.ModuleAndVersion(); + moduleIdAndVersion.setModuleId(conditionalResourceModuleId); + moduleIdAndVersion.setVersion(conditionalResourceVersion); + + ModuleConditionalResource conditionalResource = new ModuleConditionalResource(); + conditionalResource.setPath(filePath); + + final URL fileUrl = URI.create( + "file:/atomfeed/lib/jackson-mapper-asl-1.9.13.jar").toURL(); + + assertFalse(ModuleClassLoader.isMatchingConditionalResource(mockModuleV2_0, fileUrl, conditionalResource), + "Path should not match glob pattern: " + filePath); + } + + @Test + void testIsMatchingConditionalResourceWithNullConfigVersionDoesNotThrow() throws MalformedURLException { + final String conditionalResourceModuleId = "module123"; + final String conditionalResourceVersion = "1.0.0"; + ModuleConditionalResource.ModuleAndVersion moduleIdAndVersion = new ModuleConditionalResource.ModuleAndVersion(); + moduleIdAndVersion.setModuleId(conditionalResourceModuleId); + moduleIdAndVersion.setVersion(conditionalResourceVersion); + + ModuleConditionalResource conditionalResource = new ModuleConditionalResource(); + conditionalResource.setPath("/libs/jackson-mapper-asl.jar"); + + final Module moduleWithNullConfigVersions = new Module("mockmodule", "mockmodule", "org.openmrs.module.mockmodule", "author", "description", "2.0", null); + + final URL fileUrl = URI.create( + "file:/atomfeed/lib/jackson-mapper-asl-1.9.13.jar").toURL(); + assertFalse(ModuleClassLoader.isMatchingConditionalResource(moduleWithNullConfigVersions, fileUrl, conditionalResource)); + } } diff --git a/api/src/test/java/org/openmrs/module/ModuleExtensionsTest.java b/api/src/test/java/org/openmrs/module/ModuleExtensionsTest.java index cb418b60c7a5..8fe3d752c24d 100644 --- a/api/src/test/java/org/openmrs/module/ModuleExtensionsTest.java +++ b/api/src/test/java/org/openmrs/module/ModuleExtensionsTest.java @@ -47,7 +47,7 @@ public class ModuleExtensionsTest extends BaseContextMockTest { @BeforeEach public void before() { - module = new Module("Extension Test", "extensiontest", "org.openmrs.module.extensiontest", "", "", "0.0.1"); + module = new Module("Extension Test", "extensiontest", "org.openmrs.module.extensiontest", "", "", "0.0.1", "1.0"); } @AfterEach diff --git a/api/src/test/java/org/openmrs/module/ModuleFileParserTest.java b/api/src/test/java/org/openmrs/module/ModuleFileParserTest.java index 6a1096370e86..57d161615654 100644 --- a/api/src/test/java/org/openmrs/module/ModuleFileParserTest.java +++ b/api/src/test/java/org/openmrs/module/ModuleFileParserTest.java @@ -119,7 +119,7 @@ public void parse_shouldFailIfModuleHasConfigInvalidConfigVersion() throws Excep String invalidConfigVersion = "0.0.1"; String expectedMessage = messageSourceService .getMessage("Module.error.invalidConfigVersion", - new Object[] { invalidConfigVersion, "1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7" }, Context.getLocale()); + new Object[] { invalidConfigVersion, "1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 2.0" }, Context.getLocale()); Document configXml = documentBuilder.newDocument(); Element root = configXml.createElement("module"); @@ -142,6 +142,7 @@ public void parse_shouldParseValidLogicModuleFromFile() { assertThat(module.getActivatorName(), is("org.openmrs.logic.LogicModuleActivator")); assertThat(module.getMappingFiles().size(), is(1)); assertThat(module.getMappingFiles(), hasItems("LogicRuleToken.hbm.xml")); + assertThat(module.getConfigVersion(), is("1.3")); } private void expectModuleExceptionWithTranslatedMessage(Executable executable, String s) { diff --git a/api/src/test/java/org/openmrs/module/ModuleUtilTest.java b/api/src/test/java/org/openmrs/module/ModuleUtilTest.java index 68f405f848f9..2d4fd8fd0e45 100644 --- a/api/src/test/java/org/openmrs/module/ModuleUtilTest.java +++ b/api/src/test/java/org/openmrs/module/ModuleUtilTest.java @@ -36,6 +36,8 @@ import org.apache.commons.io.IOUtils; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; import org.openmrs.GlobalProperty; import org.openmrs.api.context.Context; import org.openmrs.test.jupiter.BaseContextSensitiveTest; @@ -748,6 +750,45 @@ public void getPackagesFromFile_shouldSkipOptionalFoldersIfJarFile() throws IOEx assertFalse(string.contains("web/module")); } } + + /** + * @see ModuleUtil#matchRequiredVersions(String, String) + */ + @ParameterizedTest(name = "configVersion={0}, versionRange={1} => expected={2}") + @CsvSource({ + // Exact version matches + "2.0, 2.0, true", + "2.1, 2.0, true", + "1.9, 2.0, false", + + // Wildcard major version matches + "1.5, 1.*, true", + "1.0, 1.*, true", + "2.0, 1.*, false", + + // Wildcard minor version matches + "2.0.1, 2.0.*, true", + "2.0.0, 2.0.*, true", + "2.1.0, 2.0.*, false", + + // Version range matches + "1.5, 1.0 - 2.0, true", + "2.0, 1.0 - 2.0, true", + "2.1, 1.0 - 2.0, false", + "0.9, 1.0 - 2.0, false", + + // No version range (null or empty) + "2.0, , true", + "1.5, , true", + + // Edge cases + "1.0.0, 1.0, true", + "1.0.1, 1.0, true", + "0.9.9, 1.0, false" + }) + void testMatchConfigVersions(String configVersion, String versionRange, boolean expected) { + assertEquals(expected, ModuleUtil.matchRequiredVersions(configVersion, versionRange)); + } /** * Gets Jar file to be expanded. diff --git a/api/src/test/java/org/openmrs/module/dtd/ModuleConfigDTDTest_V1_0.java b/api/src/test/java/org/openmrs/module/dtd/ModuleConfigDTDTest_V1_0.java index 2311b370e4c4..b79d51a91795 100644 --- a/api/src/test/java/org/openmrs/module/dtd/ModuleConfigDTDTest_V1_0.java +++ b/api/src/test/java/org/openmrs/module/dtd/ModuleConfigDTDTest_V1_0.java @@ -33,7 +33,7 @@ public class ModuleConfigDTDTest_V1_0 { - private static final String[] compatibleVersions = new String[] { "1.0", "1.1", "1.2", "1.3", "1.4", "1.5", "1.6", "1.7" }; + private static final String[] compatibleVersions = new String[] { "1.0", "1.1", "1.2", "1.3", "1.4", "1.5", "1.6", "1.7", "2.0" }; @ParameterizedTest @MethodSource("getCompatibleVersions") diff --git a/api/src/test/java/org/openmrs/module/dtd/ModuleConfigDTDTest_V1_1.java b/api/src/test/java/org/openmrs/module/dtd/ModuleConfigDTDTest_V1_1.java index dea2217a1dcc..12a4e9f9d036 100644 --- a/api/src/test/java/org/openmrs/module/dtd/ModuleConfigDTDTest_V1_1.java +++ b/api/src/test/java/org/openmrs/module/dtd/ModuleConfigDTDTest_V1_1.java @@ -33,7 +33,7 @@ public class ModuleConfigDTDTest_V1_1 { - private static final String[] compatibleVersions = new String[] { "1.1", "1.2", "1.3", "1.4", "1.5", "1.6", "1.7" }; + private static final String[] compatibleVersions = new String[] { "1.1", "1.2", "1.3", "1.4", "1.5", "1.6", "1.7", "2.0" }; @ParameterizedTest @MethodSource("getCompatibleVersions") diff --git a/api/src/test/java/org/openmrs/module/dtd/ModuleConfigDTDTest_V1_2.java b/api/src/test/java/org/openmrs/module/dtd/ModuleConfigDTDTest_V1_2.java index f5960eca25d8..00b9f96dcff8 100644 --- a/api/src/test/java/org/openmrs/module/dtd/ModuleConfigDTDTest_V1_2.java +++ b/api/src/test/java/org/openmrs/module/dtd/ModuleConfigDTDTest_V1_2.java @@ -31,7 +31,7 @@ public class ModuleConfigDTDTest_V1_2 { - private static final String[] compatibleVersions = new String[] {"1.2", "1.3", "1.4", "1.5", "1.6", "1.7" }; + private static final String[] compatibleVersions = new String[] {"1.2", "1.3", "1.4", "1.5", "1.6", "1.7", "2.0" }; @ParameterizedTest @MethodSource("getCompatibleVersions") diff --git a/api/src/test/java/org/openmrs/module/dtd/ModuleConfigDTDTest_V1_3.java b/api/src/test/java/org/openmrs/module/dtd/ModuleConfigDTDTest_V1_3.java index 3ac970072a0b..c9f3034ac05b 100644 --- a/api/src/test/java/org/openmrs/module/dtd/ModuleConfigDTDTest_V1_3.java +++ b/api/src/test/java/org/openmrs/module/dtd/ModuleConfigDTDTest_V1_3.java @@ -29,7 +29,7 @@ public class ModuleConfigDTDTest_V1_3 { - private static final String[] compatibleVersions = new String[] {"1.3", "1.4", "1.5", "1.6", "1.7" }; + private static final String[] compatibleVersions = new String[] {"1.3", "1.4", "1.5", "1.6", "1.7", "2.0" }; @ParameterizedTest @MethodSource("getCompatibleVersions") diff --git a/api/src/test/java/org/openmrs/module/dtd/ModuleConfigDTDTest_V1_4.java b/api/src/test/java/org/openmrs/module/dtd/ModuleConfigDTDTest_V1_4.java index 6d3656295900..a34a815d338c 100644 --- a/api/src/test/java/org/openmrs/module/dtd/ModuleConfigDTDTest_V1_4.java +++ b/api/src/test/java/org/openmrs/module/dtd/ModuleConfigDTDTest_V1_4.java @@ -39,7 +39,7 @@ public class ModuleConfigDTDTest_V1_4 { - private static final String[] compatibleVersions = new String[] {"1.4", "1.5", "1.6", "1.7" }; + private static final String[] compatibleVersions = new String[] {"1.4", "1.5", "1.6", "1.7", "2.0" }; @ParameterizedTest @MethodSource("getCompatibleVersions") diff --git a/api/src/test/java/org/openmrs/module/dtd/ModuleConfigDTDTest_V1_5.java b/api/src/test/java/org/openmrs/module/dtd/ModuleConfigDTDTest_V1_5.java index e2de5417d215..300118e82155 100644 --- a/api/src/test/java/org/openmrs/module/dtd/ModuleConfigDTDTest_V1_5.java +++ b/api/src/test/java/org/openmrs/module/dtd/ModuleConfigDTDTest_V1_5.java @@ -39,7 +39,7 @@ public class ModuleConfigDTDTest_V1_5 { - private static final String[] compatibleVersions = new String[] {"1.5", "1.6", "1.7" }; + private static final String[] compatibleVersions = new String[] {"1.5", "1.6", "1.7", "2.0" }; @ParameterizedTest @MethodSource("getCompatibleVersions") diff --git a/api/src/test/java/org/openmrs/module/dtd/ModuleConfigDTDTest_V1_6.java b/api/src/test/java/org/openmrs/module/dtd/ModuleConfigDTDTest_V1_6.java index 233f1ccce32a..0437d088bb22 100644 --- a/api/src/test/java/org/openmrs/module/dtd/ModuleConfigDTDTest_V1_6.java +++ b/api/src/test/java/org/openmrs/module/dtd/ModuleConfigDTDTest_V1_6.java @@ -36,7 +36,7 @@ public class ModuleConfigDTDTest_V1_6 { - private static final String[] compatibleVersions = new String[] { "1.6", "1.7" }; + private static final String[] compatibleVersions = new String[] { "1.6", "1.7", "2.0" }; @ParameterizedTest @MethodSource("getCompatibleVersions") diff --git a/api/src/test/java/org/openmrs/module/dtd/ModuleConfigDTDTest_V1_7.java b/api/src/test/java/org/openmrs/module/dtd/ModuleConfigDTDTest_V1_7.java index 05e4e7290c29..5f271ad12000 100644 --- a/api/src/test/java/org/openmrs/module/dtd/ModuleConfigDTDTest_V1_7.java +++ b/api/src/test/java/org/openmrs/module/dtd/ModuleConfigDTDTest_V1_7.java @@ -34,7 +34,7 @@ public class ModuleConfigDTDTest_V1_7 { - private static final String[] compatibleVersions = new String[] { "1.7" }; + private static final String[] compatibleVersions = new String[] { "1.7", "2.0" }; @ParameterizedTest @MethodSource("getCompatibleVersions") diff --git a/api/src/test/java/org/openmrs/module/dtd/ModuleConfigDTDTest_V2_0.java b/api/src/test/java/org/openmrs/module/dtd/ModuleConfigDTDTest_V2_0.java new file mode 100644 index 000000000000..a9179704739d --- /dev/null +++ b/api/src/test/java/org/openmrs/module/dtd/ModuleConfigDTDTest_V2_0.java @@ -0,0 +1,49 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public License, + * v. 2.0. If a copy of the MPL was not distributed with this file, You can + * obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under + * the terms of the Healthcare Disclaimer located at http://openmrs.org/license. + * + * Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS + * graphic logo is a trademark of OpenMRS Inc. + */ +package org.openmrs.module.dtd; + +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.openmrs.module.dtd.ConfigXmlBuilder.withMinimalTags; +import static org.openmrs.module.dtd.ConfigXmlBuilder.writeToInputStream; +import static org.openmrs.module.dtd.DtdTestValidator.isValidConfigXml; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URISyntaxException; +import java.util.Arrays; +import java.util.stream.Stream; + +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.TransformerException; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.w3c.dom.Document; + +public class ModuleConfigDTDTest_V2_0 { + + private static final String[] compatibleVersions = new String[] { "2.0" }; + + @ParameterizedTest + @MethodSource("getCompatibleVersions") + public void configXmlServletWithMinimalTags(String version) throws ParserConfigurationException, TransformerException, IOException, URISyntaxException { + Document configXml = withMinimalTags(version) + .build(); + + try (InputStream inputStream = writeToInputStream(configXml)) { + assertTrue(isValidConfigXml(inputStream)); + } + } + + private static Stream getCompatibleVersions() { + return Arrays.stream(compatibleVersions).map(Arguments::of); + } +}