Skip to content

Commit

Permalink
TRUNK-6252 Conditional resource path should be interpreted as a glob …
Browse files Browse the repository at this point in the history
…rather than a Regex (#4830)

(cherry picked from commit fd0685a)
  • Loading branch information
k4pran authored and wikumChamith committed Nov 18, 2024
1 parent 29e8245 commit dd07967
Show file tree
Hide file tree
Showing 17 changed files with 536 additions and 38 deletions.
22 changes: 20 additions & 2 deletions api/src/main/java/org/openmrs/module/Module.java
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ public final class Module {

private Set<String> packagesWithMappedClasses = new HashSet<>();

private String configVersion;

private Document config = null;

private Document sqldiff = null;
Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -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;
}
Expand Down
40 changes: 39 additions & 1 deletion api/src/main/java/org/openmrs/module/ModuleClassLoader.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion api/src/main/java/org/openmrs/module/ModuleFileParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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"));
Expand Down
124 changes: 124 additions & 0 deletions api/src/main/resources/org/openmrs/module/dtd/config-2.0.dtd
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Top level configuration element.
-->
<!ELEMENT module (
(id),
(name),
(version),
(package),
(author),
(description),
(activator),
(updateURL?),
(require_version?),
(require_database_version?),
(require_modules?),
(aware_of_modules?),
(start_before_modules?),
(mandatory?),
(library*),
(extension*),
(advice*),
(privilege*),
(globalProperty*),
(dwr?),
(servlet*),
(filter*),
(filter-mapping*),
(messages*),
(mappingFiles?),
(packagesWithMappedClasses?),
(conditionalResources?)
)>
<!ATTLIST module configVersion CDATA #FIXED "2.0">

<!ELEMENT id (#PCDATA)>
<!ELEMENT name (#PCDATA)>
<!ELEMENT version (#PCDATA)>
<!ELEMENT package (#PCDATA)>
<!ELEMENT author (#PCDATA)>
<!ELEMENT description (#PCDATA)>
<!ELEMENT activator (#PCDATA)>
<!ELEMENT updateURL (#PCDATA)>
<!ELEMENT require_version (#PCDATA)>
<!ELEMENT require_database_version (#PCDATA)>

<!ELEMENT require_modules (require_module+)>
<!ELEMENT require_module (#PCDATA)>
<!ATTLIST require_module version CDATA #IMPLIED>

<!ELEMENT aware_of_modules (aware_of_module+)>
<!ELEMENT aware_of_module (#PCDATA)>
<!ATTLIST aware_of_module version CDATA #IMPLIED>

<!ELEMENT start_before_modules (module+)>
<!ELEMENT start_before_module (#PCDATA)>
<!ATTLIST start_before_module version CDATA #IMPLIED>

<!ELEMENT mandatory (#PCDATA)>

<!ELEMENT library EMPTY>
<!ATTLIST library
id CDATA #REQUIRED
path CDATA #REQUIRED
type (resources|library) #REQUIRED
>

<!ELEMENT extension (point, class)>
<!ELEMENT advice (point, class)>
<!ELEMENT point (#PCDATA)>
<!ELEMENT class (#PCDATA)>

<!ELEMENT privilege (name, description)>

<!ELEMENT globalProperty (property, defaultValue?, description)>
<!ELEMENT property (#PCDATA)>
<!ELEMENT defaultValue (#PCDATA)>

<!ELEMENT dwr (allow, signatures?)>
<!ELEMENT allow (create*, convert*)>

<!ELEMENT create (param, include*)>
<!ATTLIST create creator CDATA #REQUIRED javascript CDATA #REQUIRED>

<!ELEMENT param EMPTY>
<!ATTLIST param name CDATA #REQUIRED value CDATA #REQUIRED>

<!ELEMENT include EMPTY>
<!ATTLIST include method CDATA #REQUIRED>

<!ELEMENT convert (param?)>
<!ATTLIST convert converter CDATA #REQUIRED match CDATA #REQUIRED>

<!ELEMENT signatures (#PCDATA)>

<!ELEMENT servlet (servlet-name, servlet-class, init-param*)>
<!ELEMENT servlet-name (#PCDATA)>
<!ELEMENT servlet-class (#PCDATA)>

<!ELEMENT filter (filter-name, filter-class, init-param*)>
<!ELEMENT filter-name (#PCDATA)>
<!ELEMENT filter-class (#PCDATA)>
<!ELEMENT init-param (param-name, param-value)>
<!ELEMENT param-name (#PCDATA)>
<!ELEMENT param-value (#PCDATA)>

<!ELEMENT filter-mapping (filter-name, (url-pattern | servlet-name))>
<!ELEMENT url-pattern (#PCDATA)>

<!ELEMENT messages (lang, file)>
<!ELEMENT lang (#PCDATA)>
<!ELEMENT file (#PCDATA)>

<!ELEMENT mappingFiles (#PCDATA)>
<!ELEMENT packagesWithMappedClasses (#PCDATA)>

<!ELEMENT conditionalResources (conditionalResource+)>
<!-- Beginning with configVersion="2.0", the "path" in <conditionalResource> uses glob syntax instead of regex. -->
<!ELEMENT conditionalResource (path, (openmrsVersion | modules))>
<!ELEMENT path (#PCDATA)>
<!ELEMENT openmrsVersion (#PCDATA)>
<!ELEMENT modules (module+)>
<!ELEMENT module (moduleId, version)>
<!ELEMENT moduleId (#PCDATA)>
Loading

0 comments on commit dd07967

Please sign in to comment.