Skip to content

Commit ee0d7ca

Browse files
committedFeb 7, 2014
TRUNK-3644 Define which resources to load for module based on OpenMRS version
(cherry picked from commit a47d42e)
1 parent 5899a69 commit ee0d7ca

19 files changed

+1060
-153
lines changed
 

‎api/pom.xml

+16
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,22 @@
200200
</dependency>
201201
</dependencies>
202202
<build>
203+
<resources>
204+
<resource>
205+
<directory>src/main/resources</directory>
206+
<filtering>true</filtering>
207+
<includes>
208+
<include>org/openmrs/api/openmrs.properties</include>
209+
</includes>
210+
</resource>
211+
<resource>
212+
<directory>src/main/resources</directory>
213+
<filtering>false</filtering>
214+
<excludes>
215+
<exclude>org/openmrs/api/openmrs.properties</exclude>
216+
</excludes>
217+
</resource>
218+
</resources>
203219
<plugins>
204220
<plugin>
205221
<groupId>org.apache.maven.plugins</groupId>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/**
2+
* The contents of this file are subject to the OpenMRS Public License
3+
* Version 1.0 (the "License"); you may not use this file except in
4+
* compliance with the License. You may obtain a copy of the License at
5+
* http://license.openmrs.org
6+
*
7+
* Software distributed under the License is distributed on an "AS IS"
8+
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
9+
* License for the specific language governing rights and limitations
10+
* under the License.
11+
*
12+
* Copyright (C) OpenMRS, LLC. All Rights Reserved.
13+
*/
14+
package org.openmrs.annotation;
15+
16+
import java.lang.annotation.Documented;
17+
import java.lang.annotation.ElementType;
18+
import java.lang.annotation.Inherited;
19+
import java.lang.annotation.Retention;
20+
import java.lang.annotation.RetentionPolicy;
21+
import java.lang.annotation.Target;
22+
23+
/**
24+
* Placed on beans which should be loaded conditionally based on OpenMRS version or started modules.
25+
*
26+
* @since 1.10, 1.9.8, 1.8.5, 1.7.5
27+
*/
28+
@Target( { ElementType.TYPE })
29+
@Retention(RetentionPolicy.RUNTIME)
30+
@Inherited
31+
@Documented
32+
public @interface OpenmrsProfile {
33+
34+
public String openmrsVersion() default "";
35+
36+
public String[] modules() default {};
37+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/**
2+
* The contents of this file are subject to the OpenMRS Public License
3+
* Version 1.0 (the "License"); you may not use this file except in
4+
* compliance with the License. You may obtain a copy of the License at
5+
* http://license.openmrs.org
6+
*
7+
* Software distributed under the License is distributed on an "AS IS"
8+
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
9+
* License for the specific language governing rights and limitations
10+
* under the License.
11+
*
12+
* Copyright (C) OpenMRS, LLC. All Rights Reserved.
13+
*/
14+
package org.openmrs.annotation;
15+
16+
import org.apache.commons.lang.StringUtils;
17+
import org.openmrs.module.Module;
18+
import org.openmrs.module.ModuleFactory;
19+
import org.openmrs.module.ModuleUtil;
20+
import org.openmrs.util.OpenmrsConstants;
21+
import org.springframework.core.type.classreading.MetadataReader;
22+
import org.springframework.core.type.classreading.MetadataReaderFactory;
23+
import org.springframework.core.type.filter.TypeFilter;
24+
25+
import java.io.IOException;
26+
import java.util.Map;
27+
28+
/**
29+
* Creates a bean only if profile is matched. It returns true if a bean should be excluded.
30+
*/
31+
public class OpenmrsProfileExclusionFilter implements TypeFilter {
32+
33+
/**
34+
* @param metadataReader
35+
* @param metadataReaderFactory
36+
* @return
37+
* @throws IOException
38+
*
39+
* @should not include bean for openmrs from 1_6 to 1_8
40+
* @should not include bean for openmrs 1_10 and later
41+
* @should not include bean for openmrs 1_9 and later if module missing
42+
* @should include bean for openmrs 1_9 and later
43+
*/
44+
@Override
45+
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
46+
Map<String, Object> openmrsProfile = metadataReader.getAnnotationMetadata().getAnnotationAttributes(
47+
"org.openmrs.annotation.OpenmrsProfile");
48+
if (openmrsProfile != null) {
49+
Object openmrsVersion = openmrsProfile.get("openmrsVersion");
50+
if (StringUtils.isNotBlank((String) openmrsVersion)) {
51+
if (!ModuleUtil.matchRequiredVersions(OpenmrsConstants.OPENMRS_VERSION_SHORT, (String) openmrsVersion)) {
52+
return true; //exclude
53+
}
54+
}
55+
56+
String[] modules = (String[]) openmrsProfile.get("modules");
57+
58+
for (String moduleAndVersion : modules) {
59+
boolean exclude = true;
60+
61+
String[] splitModuleAndVersion = moduleAndVersion.split(":");
62+
String moduleId = splitModuleAndVersion[0];
63+
String moduleVersion = splitModuleAndVersion[1];
64+
65+
for (Module module : ModuleFactory.getStartedModules()) {
66+
if (module.getModuleId().equals(moduleId)) {
67+
if (ModuleUtil.matchRequiredVersions(module.getVersion(), moduleVersion)) {
68+
exclude = false;
69+
break;
70+
}
71+
}
72+
}
73+
74+
if (exclude) {
75+
return exclude;
76+
}
77+
}
78+
}
79+
80+
return false; //include
81+
}
82+
}

‎api/src/main/java/org/openmrs/module/Module.java

+14
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,8 @@ public final class Module {
9595

9696
private boolean mandatory = Boolean.FALSE;
9797

98+
private List<ModuleConditionalResource> conditionalResources = new ArrayList<ModuleConditionalResource>();
99+
98100
// keep a reference to the file that we got this module from so we can delete
99101
// it if necessary
100102
private File file = null;
@@ -361,6 +363,10 @@ public List<String> getAwareOfModules() {
361363
return awareOfModulesMap == null ? null : new ArrayList<String>(awareOfModulesMap.keySet());
362364
}
363365

366+
public String getAwareOfModuleVersion(String awareOfModule) {
367+
return awareOfModulesMap == null ? null : awareOfModulesMap.get(awareOfModule);
368+
}
369+
364370
/**
365371
* @return the requireOpenmrsVersion
366372
*/
@@ -743,4 +749,12 @@ public void disposeAdvicePointsClassInstance() {
743749
advicePoint.disposeClassInstance();
744750
}
745751
}
752+
753+
public List<ModuleConditionalResource> getConditionalResources() {
754+
return conditionalResources;
755+
}
756+
757+
public void setConditionalResources(List<ModuleConditionalResource> conditionalResources) {
758+
this.conditionalResources = conditionalResources;
759+
}
746760
}

‎api/src/main/java/org/openmrs/module/ModuleClassLoader.java

+97-20
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import java.util.Collection;
3333
import java.util.Collections;
3434
import java.util.Enumeration;
35+
import java.util.HashMap;
3536
import java.util.HashSet;
3637
import java.util.Iterator;
3738
import java.util.LinkedHashSet;
@@ -42,10 +43,12 @@
4243
import java.util.WeakHashMap;
4344

4445
import org.apache.commons.io.FileUtils;
46+
import org.apache.commons.lang.StringUtils;
4547
import org.apache.commons.logging.Log;
4648
import org.apache.commons.logging.LogFactory;
4749
import org.openmrs.api.APIException;
4850
import org.openmrs.util.OpenmrsClassLoader;
51+
import org.openmrs.util.OpenmrsConstants;
4952
import org.openmrs.util.OpenmrsUtil;
5053

5154
/**
@@ -85,8 +88,8 @@ protected ModuleClassLoader(final Module module, final List<URL> urls, final Cla
8588
log.debug("URLs length: " + urls.size());
8689

8790
this.module = module;
88-
collectRequiredModuleImports();
89-
collectAwareOfModuleImports();
91+
requiredModules = collectRequiredModuleImports(module);
92+
awareOfModules = collectAwareOfModuleImports(module);
9093
collectFilters();
9194
libraryCache = new WeakHashMap<URI, File>();
9295
}
@@ -214,12 +217,32 @@ private static List<URL> getUrls(final Module module) {
214217
File libdir = new File(tmpModuleDir, "lib");
215218

216219
if (libdir != null && libdir.exists()) {
220+
Map<String, String> startedRelatedModules = new HashMap<String, String>();
221+
for (Module requiredModule : collectRequiredModuleImports(module)) {
222+
startedRelatedModules.put(requiredModule.getModuleId(), requiredModule.getVersion());
223+
}
224+
for (Module awareOfModule : collectAwareOfModuleImports(module)) {
225+
startedRelatedModules.put(awareOfModule.getModuleId(), awareOfModule.getVersion());
226+
}
227+
217228
// recursively get files
218229
Collection<File> files = (Collection<File>) FileUtils.listFiles(libdir, new String[] { "jar" }, true);
219230
for (File file : files) {
220-
if (log.isDebugEnabled())
221-
log.debug("Adding file to results: " + file.getAbsolutePath());
222-
result.add(ModuleUtil.file2url(file));
231+
URL fileUrl = ModuleUtil.file2url(file);
232+
233+
boolean include = shouldResourceBeIncluded(module, fileUrl, OpenmrsConstants.OPENMRS_VERSION_SHORT,
234+
startedRelatedModules);
235+
236+
if (include) {
237+
if (log.isDebugEnabled()) {
238+
log.debug("Including file in classpath: " + fileUrl);
239+
}
240+
result.add(fileUrl);
241+
} else {
242+
if (log.isDebugEnabled()) {
243+
log.debug("Excluding file from classpath: " + fileUrl);
244+
}
245+
}
223246
}
224247
}
225248
}
@@ -235,6 +258,63 @@ private static List<URL> getUrls(final Module module) {
235258
return result;
236259
}
237260

261+
/**
262+
* Determines whether or not the given resource should be available on the classpath based on
263+
* OpenMRS version and/or modules' version. It uses the conditionalResources section specified in config.xml.
264+
*
265+
* Resources that are not mentioned as conditional resources are included by default.
266+
*
267+
* All conditions for a conditional resource to be included must match.
268+
*
269+
* @param module
270+
* @param fileUrl
271+
* @return true if it should be included
272+
* @should return true if file matches and openmrs version matches
273+
* @should return false if file matches but openmrs version does not
274+
* @should return true if file does not match and openmrs version does not match
275+
* @should return true if file matches and module version matches
276+
* @should return false if file matches and module version does not match
277+
* @should return false if file matches and openmrs version matches but module version does not match
278+
* @should return false if file matches and module not found
279+
* @should return true if file does not match and module version does not match
280+
*/
281+
static boolean shouldResourceBeIncluded(Module module, URL fileUrl, String openmrsVersion,
282+
Map<String, String> startedRelatedModules) {
283+
boolean include = true; //all resources are included by default
284+
285+
for (ModuleConditionalResource conditionalResource : module.getConditionalResources()) {
286+
if (fileUrl.getPath().matches(".*" + conditionalResource.getPath() + "$")) {
287+
include = false; //if a resource matches a path of contidionalResource then it must meet all conditions
288+
289+
if (StringUtils.isNotBlank(conditionalResource.getOpenmrsVersion())) { //openmrsVersion is optional
290+
include = ModuleUtil.matchRequiredVersions(openmrsVersion, conditionalResource.getOpenmrsVersion());
291+
292+
if (!include) {
293+
return false;
294+
}
295+
}
296+
297+
if (conditionalResource.getModules() != null) { //modules are optional
298+
for (ModuleConditionalResource.ModuleAndVersion conditionalModuleResource : conditionalResource
299+
.getModules()) {
300+
String moduleVersion = startedRelatedModules.get(conditionalModuleResource.getModuleId());
301+
if (moduleVersion != null) {
302+
include = ModuleUtil
303+
.matchRequiredVersions(moduleVersion, conditionalModuleResource.getVersion());
304+
305+
if (!include) {
306+
return false;
307+
}
308+
}
309+
}
310+
311+
}
312+
}
313+
}
314+
315+
return include;
316+
}
317+
238318
/**
239319
* Get the library cache folder for the given module. Each module has a different cache folder
240320
* to ease cleanup when unloading a module while openmrs is running
@@ -277,51 +357,50 @@ private static List<URL> getUrls(final Module module, final URL[] existingUrls)
277357
* Get and cache the imports for this module. The imports should just be the modules that set as
278358
* "required" by this module
279359
*/
280-
protected void collectRequiredModuleImports() {
360+
protected static Module[] collectRequiredModuleImports(Module module) {
281361
// collect imported modules (exclude duplicates)
282362
Map<String, Module> publicImportsMap = new WeakHashMap<String, Module>(); //<module ID, Module>
283363

284364
for (String moduleId : ModuleConstants.CORE_MODULES.keySet()) {
285-
Module module = ModuleFactory.getModuleById(moduleId);
365+
Module coreModule = ModuleFactory.getModuleById(moduleId);
286366

287-
if (module == null && !ModuleUtil.ignoreCoreModules()) {
367+
if (coreModule == null && !ModuleUtil.ignoreCoreModules()) {
288368
log.error("Unable to find an openmrs core loaded module with id: " + moduleId);
289369
throw new APIException(
290370
"Should not be here. All 'core' required modules by the api should be started and their classloaders should be available");
291371
}
292372

293373
// if this is already the classloader for one of the core modules, don't put it on the import list
294-
if (module != null && !moduleId.equals(this.getModule().getModuleId())) {
295-
publicImportsMap.put(moduleId, module);
374+
if (coreModule != null && !moduleId.equals(module.getModuleId())) {
375+
publicImportsMap.put(moduleId, coreModule);
296376
}
297377
}
298378

299-
for (String requiredPackage : getModule().getRequiredModules()) {
379+
for (String requiredPackage : module.getRequiredModules()) {
300380
Module requiredModule = ModuleFactory.getModuleByPackage(requiredPackage);
301381
if (ModuleFactory.isModuleStarted(requiredModule)) {
302382
publicImportsMap.put(requiredModule.getModuleId(), requiredModule);
303383
}
304384
}
305-
requiredModules = publicImportsMap.values().toArray(new Module[publicImportsMap.size()]);
385+
return publicImportsMap.values().toArray(new Module[publicImportsMap.size()]);
306386

307387
}
308388

309389
/**
310390
* Get and cache the imports for this module. The imports should just be the modules that set as
311391
* "aware of" by this module
312392
*/
313-
protected void collectAwareOfModuleImports() {
393+
protected static Module[] collectAwareOfModuleImports(Module module) {
314394
// collect imported modules (exclude duplicates)
315395
Map<String, Module> publicImportsMap = new WeakHashMap<String, Module>(); //<module ID, Module>
316396

317-
for (String awareOfPackage : getModule().getAwareOfModules()) {
397+
for (String awareOfPackage : module.getAwareOfModules()) {
318398
Module awareOfModule = ModuleFactory.getModuleByPackage(awareOfPackage);
319399
if (ModuleFactory.isModuleStarted(awareOfModule)) {
320400
publicImportsMap.put(awareOfModule.getModuleId(), awareOfModule);
321401
}
322402
}
323-
awareOfModules = publicImportsMap.values().toArray(new Module[publicImportsMap.size()]);
324-
403+
return publicImportsMap.values().toArray(new Module[publicImportsMap.size()]);
325404
}
326405

327406
/**
@@ -361,8 +440,8 @@ protected void modulesSetChanged() {
361440
}
362441
log.debug(buf.toString());
363442
}
364-
collectRequiredModuleImports();
365-
collectAwareOfModuleImports();
443+
requiredModules = collectRequiredModuleImports(getModule());
444+
awareOfModules = collectAwareOfModuleImports(getModule());
366445
// repopulate resource URLs
367446
//resourceLoader = ModuleResourceLoader.get(getModule());
368447
collectFilters();
@@ -1021,7 +1100,6 @@ public void setAdditionalPackages(Set<String> additionalPackages) {
10211100
* module
10221101
*
10231102
* @param additionalPackage string package name
1024-
* @see #setProvidedPackages(Set)
10251103
*/
10261104
public void addAdditionalPackage(String additionalPackage) {
10271105
if (this.additionalPackages == null)
@@ -1038,7 +1116,6 @@ public void addAdditionalPackage(String additionalPackage) {
10381116
* module
10391117
*
10401118
* @param providedPackages list/set of strings that are package names
1041-
* @see #setProvidedPackages(Set)
10421119
*/
10431120
public void addAllAdditionalPackages(Collection<String> providedPackages) {
10441121
if (this.additionalPackages == null)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
/**
2+
* The contents of this file are subject to the OpenMRS Public License
3+
* Version 1.0 (the "License"); you may not use this file except in
4+
* compliance with the License. You may obtain a copy of the License at
5+
* http://license.openmrs.org
6+
*
7+
* Software distributed under the License is distributed on an "AS IS"
8+
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
9+
* License for the specific language governing rights and limitations
10+
* under the License.
11+
*
12+
* Copyright (C) OpenMRS, LLC. All Rights Reserved.
13+
*/
14+
package org.openmrs.module;
15+
16+
import java.util.ArrayList;
17+
import java.util.List;
18+
19+
/**
20+
* Allows to specify a conditionally loaded resource in a module based on
21+
* OpenMRS version and/or modules listed as required or aware of.
22+
*
23+
* @since 1.10, 1.9.8, 1.8.5, 1.7.5
24+
*/
25+
public class ModuleConditionalResource {
26+
27+
private String path;
28+
29+
private String openmrsVersion;
30+
31+
private List<ModuleAndVersion> modules = new ArrayList<ModuleAndVersion>();
32+
33+
public String getPath() {
34+
return path;
35+
}
36+
37+
public void setPath(String path) {
38+
this.path = path;
39+
}
40+
41+
public String getOpenmrsVersion() {
42+
return openmrsVersion;
43+
}
44+
45+
public void setOpenmrsVersion(String openmrsVersion) {
46+
this.openmrsVersion = openmrsVersion;
47+
}
48+
49+
public List<ModuleAndVersion> getModules() {
50+
return modules;
51+
}
52+
53+
public void setModules(List<ModuleAndVersion> modules) {
54+
this.modules = modules;
55+
}
56+
57+
@Override
58+
public boolean equals(Object o) {
59+
if (this == o)
60+
return true;
61+
if (o == null || getClass() != o.getClass())
62+
return false;
63+
64+
ModuleConditionalResource that = (ModuleConditionalResource) o;
65+
66+
if (modules != null ? !modules.equals(that.modules) : that.modules != null)
67+
return false;
68+
if (openmrsVersion != null ? !openmrsVersion.equals(that.openmrsVersion) : that.openmrsVersion != null)
69+
return false;
70+
if (path != null ? !path.equals(that.path) : that.path != null)
71+
return false;
72+
73+
return true;
74+
}
75+
76+
@Override
77+
public String toString() {
78+
return "ModuleConditionalResource{" + "path='" + path + '\'' + ", openmrsVersion='" + openmrsVersion + '\''
79+
+ ", modules=" + modules + '}';
80+
}
81+
82+
@Override
83+
public int hashCode() {
84+
int result = path != null ? path.hashCode() : 0;
85+
result = 31 * result + (openmrsVersion != null ? openmrsVersion.hashCode() : 0);
86+
result = 31 * result + (modules != null ? modules.hashCode() : 0);
87+
return result;
88+
}
89+
90+
public static class ModuleAndVersion {
91+
92+
private String moduleId;
93+
94+
private String version;
95+
96+
public String getModuleId() {
97+
return moduleId;
98+
}
99+
100+
public void setModuleId(String moduleId) {
101+
this.moduleId = moduleId;
102+
}
103+
104+
public String getVersion() {
105+
return version;
106+
}
107+
108+
public void setVersion(String version) {
109+
this.version = version;
110+
}
111+
112+
@Override
113+
public boolean equals(Object o) {
114+
if (this == o)
115+
return true;
116+
if (o == null || getClass() != o.getClass())
117+
return false;
118+
119+
ModuleAndVersion that = (ModuleAndVersion) o;
120+
121+
if (moduleId != null ? !moduleId.equals(that.moduleId) : that.moduleId != null)
122+
return false;
123+
if (version != null ? !version.equals(that.version) : that.version != null)
124+
return false;
125+
126+
return true;
127+
}
128+
129+
@Override
130+
public int hashCode() {
131+
int result = moduleId != null ? moduleId.hashCode() : 0;
132+
result = 31 * result + (version != null ? version.hashCode() : 0);
133+
return result;
134+
}
135+
136+
@Override
137+
public String toString() {
138+
return "ModuleAndVersion{" + "moduleId='" + moduleId + '\'' + ", version='" + version + '\'' + '}';
139+
}
140+
}
141+
}

‎api/src/main/java/org/openmrs/module/ModuleFileParser.java

+84
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import javax.xml.parsers.DocumentBuilder;
3737
import javax.xml.parsers.DocumentBuilderFactory;
3838

39+
import org.apache.commons.lang3.StringUtils;
3940
import org.apache.commons.logging.Log;
4041
import org.apache.commons.logging.LogFactory;
4142
import org.openmrs.GlobalProperty;
@@ -122,6 +123,9 @@ public ModuleFileParser(InputStream inputStream) {
122123
}
123124
}
124125

126+
ModuleFileParser() {
127+
}
128+
125129
/**
126130
* Get the module
127131
*
@@ -287,6 +291,8 @@ public InputSource resolveEntity(String publicId, String systemId) throws SAXExc
287291
module.setMandatory(getMandatory(rootNode, configVersion, jarfile));
288292

289293
module.setFile(moduleFile);
294+
295+
module.setConditionalResources(getConditionalResources(rootNode));
290296
}
291297
finally {
292298
try {
@@ -308,6 +314,84 @@ public InputSource resolveEntity(String publicId, String systemId) throws SAXExc
308314
return module;
309315
}
310316

317+
/**
318+
* Parses conditionalResources tag.
319+
* @param rootNode
320+
* @return
321+
*
322+
* @should parse openmrsVersion and modules
323+
* @should parse conditionalResource with whitespace
324+
* @should throw exception if multiple conditionalResources tags found
325+
* @should throw exception if conditionalResources contains invalid tag
326+
* @should throw exception if path is blank
327+
*/
328+
List<ModuleConditionalResource> getConditionalResources(Element rootNode) {
329+
List<ModuleConditionalResource> conditionalResources = new ArrayList<ModuleConditionalResource>();
330+
331+
NodeList parentConditionalResources = rootNode.getElementsByTagName("conditionalResources");
332+
333+
if (parentConditionalResources.getLength() == 0) {
334+
return new ArrayList<ModuleConditionalResource>();
335+
} else if (parentConditionalResources.getLength() > 1) {
336+
throw new IllegalArgumentException("Found multiple conditionalResources tags. There can be only one.");
337+
}
338+
339+
NodeList conditionalResourcesNode = parentConditionalResources.item(0).getChildNodes();
340+
341+
for (int i = 0; i < conditionalResourcesNode.getLength(); i++) {
342+
Node conditionalResourceNode = conditionalResourcesNode.item(i);
343+
344+
if ("#text".equals(conditionalResourceNode.getNodeName())) {
345+
continue; //ignore text and whitespace in particular
346+
}
347+
348+
if (!"conditionalResource".equals(conditionalResourceNode.getNodeName())) {
349+
throw new IllegalArgumentException("Found the " + conditionalResourceNode.getNodeName()
350+
+ " node under conditionalResources. Only conditionalResource is allowed.");
351+
}
352+
353+
NodeList resourceElements = conditionalResourceNode.getChildNodes();
354+
355+
ModuleConditionalResource resource = new ModuleConditionalResource();
356+
conditionalResources.add(resource);
357+
358+
for (int j = 0; j < resourceElements.getLength(); j++) {
359+
Node resourceElement = resourceElements.item(j);
360+
361+
if ("path".equals(resourceElement.getNodeName())) {
362+
if (StringUtils.isBlank(resourceElement.getTextContent())) {
363+
throw new IllegalArgumentException("The path of a conditional resource must not be blank");
364+
}
365+
resource.setPath(resourceElement.getTextContent());
366+
} else if ("openmrsVersion".equals(resourceElement.getNodeName())) {
367+
resource.setOpenmrsVersion(resourceElement.getTextContent());
368+
} else if ("modules".equals(resourceElement.getNodeName())) {
369+
NodeList modulesNode = resourceElement.getChildNodes();
370+
for (int k = 0; k < modulesNode.getLength(); k++) {
371+
Node moduleNode = modulesNode.item(k);
372+
if ("module".equals(moduleNode.getNodeName())) {
373+
NodeList moduleElements = moduleNode.getChildNodes();
374+
375+
ModuleConditionalResource.ModuleAndVersion module = new ModuleConditionalResource.ModuleAndVersion();
376+
resource.getModules().add(module);
377+
for (int m = 0; m < moduleElements.getLength(); m++) {
378+
Node moduleElement = moduleElements.item(m);
379+
380+
if ("moduleId".equals(moduleElement.getNodeName())) {
381+
module.setModuleId(moduleElement.getTextContent());
382+
} else if ("version".equals(moduleElement.getNodeName())) {
383+
module.setVersion(moduleElement.getTextContent());
384+
}
385+
}
386+
}
387+
}
388+
}
389+
}
390+
}
391+
392+
return conditionalResources;
393+
}
394+
311395
/**
312396
* Generic method to get a module tag
313397
*

‎api/src/main/java/org/openmrs/module/ModuleUtil.java

+64-67
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@
4646
import org.openmrs.GlobalProperty;
4747
import org.openmrs.api.AdministrationService;
4848
import org.openmrs.api.context.Context;
49-
import org.openmrs.api.context.Daemon;
5049
import org.openmrs.api.context.ServiceContext;
5150
import org.openmrs.util.OpenmrsClassLoader;
5251
import org.openmrs.util.OpenmrsUtil;
@@ -221,7 +220,7 @@ public static File insertModuleFile(InputStream inputStream, String filename) {
221220
* </ul>
222221
*
223222
* @param version openmrs version number to be compared
224-
* @param value value in the config file for required openmrs version
223+
* @param versionRange value in the config file for required openmrs version
225224
* @return true if the <code>version</code> is within the <code>value</code>
226225
* @should allow ranged required version
227226
* @should allow ranged required version with wild card
@@ -239,18 +238,64 @@ public static File insertModuleFile(InputStream inputStream, String filename) {
239238
* @should return false when single entry required version beyond openmrs version
240239
* @should allow release type in the version
241240
*/
242-
public static boolean matchRequiredVersions(String version, String value) {
243-
try {
244-
/*
245-
* If "value" is not within range specified by "version", then a ModuleException will be thrown.
246-
* Otherwise, just return true at last.
247-
*/
248-
checkRequiredVersion(version, value);
249-
return true;
250-
}
251-
catch (ModuleException e) {
252-
return false;
241+
public static boolean matchRequiredVersions(String version, String versionRange) {
242+
if (versionRange != null && !versionRange.equals("")) {
243+
String[] ranges = versionRange.split(",");
244+
for (String range : ranges) {
245+
// need to externalize this string
246+
String separator = "-";
247+
if (range.indexOf("*") > 0 || range.indexOf(separator) > 0) {
248+
// if it contains "*" or "-" then we must separate those two
249+
// assume it's always going to be two part
250+
// assign the upper and lower bound
251+
// if there's no "-" to split lower and upper bound
252+
// then assign the same value for the lower and upper
253+
String lowerBound = range;
254+
String upperBound = range;
255+
256+
int indexOfSeparator = range.indexOf(separator);
257+
while (indexOfSeparator > 0) {
258+
lowerBound = range.substring(0, indexOfSeparator);
259+
upperBound = range.substring(indexOfSeparator + 1);
260+
if (upperBound.matches("^\\s?\\d+.*"))
261+
break;
262+
indexOfSeparator = range.indexOf(separator, indexOfSeparator + 1);
263+
}
264+
265+
// only preserve part of the string that match the following format:
266+
// - xx.yy.*
267+
// - xx.yy.zz*
268+
lowerBound = StringUtils.remove(lowerBound, lowerBound.replaceAll("^\\s?\\d+[\\.\\d+\\*?|\\.\\*]+", ""));
269+
upperBound = StringUtils.remove(upperBound, upperBound.replaceAll("^\\s?\\d+[\\.\\d+\\*?|\\.\\*]+", ""));
270+
271+
// if the lower contains "*" then change it to zero
272+
if (lowerBound.indexOf("*") > 0)
273+
lowerBound = lowerBound.replaceAll("\\*", "0");
274+
275+
// if the upper contains "*" then change it to 999
276+
// assuming 999 will be the max revision number for openmrs
277+
if (upperBound.indexOf("*") > 0)
278+
upperBound = upperBound.replaceAll("\\*", "999");
279+
280+
int lowerReturn = compareVersion(version, lowerBound);
281+
282+
int upperReturn = compareVersion(version, upperBound);
283+
284+
if (lowerReturn < 0 || upperReturn > 0) {
285+
log.debug("Version " + version + " is not between " + lowerBound + " and " + upperBound);
286+
} else {
287+
return true;
288+
}
289+
} else {
290+
if (compareVersion(version, range) < 0) {
291+
log.debug("Version " + version + " is below " + range);
292+
} else {
293+
return true;
294+
}
295+
}
296+
}
253297
}
298+
return false;
254299
}
255300

256301
/**
@@ -268,7 +313,7 @@ public static boolean matchRequiredVersions(String version, String value) {
268313
* <br/>
269314
*
270315
* @param version openmrs version number to be compared
271-
* @param value value in the config file for required openmrs version
316+
* @param versionRange value in the config file for required openmrs version
272317
* @throws ModuleException if the <code>version</code> is not within the <code>value</code>
273318
* @should throw ModuleException if openmrs version beyond wild card range
274319
* @should throw ModuleException if required version beyond openmrs version
@@ -277,59 +322,11 @@ public static boolean matchRequiredVersions(String version, String value) {
277322
* version
278323
* @should throw ModuleException if single entry required version beyond openmrs version
279324
*/
280-
public static void checkRequiredVersion(String version, String value) throws ModuleException {
281-
if (value != null && !value.equals("")) {
282-
// need to externalize this string
283-
String separator = "-";
284-
if (value.indexOf("*") > 0 || value.indexOf(separator) > 0) {
285-
// if it contains "*" or "-" then we must separate those two
286-
// assume it's always going to be two part
287-
// assign the upper and lower bound
288-
// if there's no "-" to split lower and upper bound
289-
// then assign the same value for the lower and upper
290-
String lowerBound = value;
291-
String upperBound = value;
292-
293-
int indexOfSeparator = value.indexOf(separator);
294-
while (indexOfSeparator > 0) {
295-
lowerBound = value.substring(0, indexOfSeparator);
296-
upperBound = value.substring(indexOfSeparator + 1);
297-
if (upperBound.matches("^\\s?\\d+.*"))
298-
break;
299-
indexOfSeparator = value.indexOf(separator, indexOfSeparator + 1);
300-
}
301-
302-
// only preserve part of the string that match the following format:
303-
// - xx.yy.*
304-
// - xx.yy.zz*
305-
lowerBound = StringUtils.remove(lowerBound, lowerBound.replaceAll("^\\s?\\d+[\\.\\d+\\*?|\\.\\*]+", ""));
306-
upperBound = StringUtils.remove(upperBound, upperBound.replaceAll("^\\s?\\d+[\\.\\d+\\*?|\\.\\*]+", ""));
307-
308-
// if the lower contains "*" then change it to zero
309-
if (lowerBound.indexOf("*") > 0)
310-
lowerBound = lowerBound.replaceAll("\\*", "0");
311-
312-
// if the upper contains "*" then change it to 999
313-
// assuming 999 will be the max revision number for openmrs
314-
if (upperBound.indexOf("*") > 0)
315-
upperBound = upperBound.replaceAll("\\*", "999");
316-
317-
int lowerReturn = compareVersion(version, lowerBound);
318-
319-
int upperReturn = compareVersion(version, upperBound);
320-
321-
if (lowerReturn < 0 || upperReturn > 0) {
322-
String ms = Context.getMessageSourceService().getMessage("Module.requireVersion.outOfBounds",
323-
new String[] { lowerBound, upperBound, version }, Context.getLocale());
324-
throw new ModuleException(ms);
325-
}
326-
} else {
327-
if (compareVersion(version, value) < 0) {
328-
String ms = Context.getMessageSourceService().getMessage("Module.requireVersion.belowLowerBound",
329-
new String[] { value, version }, Context.getLocale());
330-
throw new ModuleException(ms);
331-
}
332-
}
325+
public static void checkRequiredVersion(String version, String versionRange) throws ModuleException {
326+
if (!matchRequiredVersions(version, versionRange)) {
327+
String ms = Context.getMessageSourceService().getMessage("Module.requireVersion.outOfBounds",
328+
new String[] { versionRange, version }, Context.getLocale());
329+
throw new ModuleException(ms);
333330
}
334331
}
335332

‎api/src/main/java/org/openmrs/util/OpenmrsConstants.java

+24-63
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
package org.openmrs.util;
1515

1616
import java.io.IOException;
17+
import java.io.InputStream;
1718
import java.util.Collection;
1819
import java.util.HashMap;
1920
import java.util.LinkedHashMap;
@@ -23,6 +24,7 @@
2324
import java.util.Properties;
2425
import java.util.Vector;
2526

27+
import org.apache.commons.io.IOUtils;
2628
import org.apache.commons.logging.Log;
2729
import org.apache.commons.logging.LogFactory;
2830
import org.openmrs.GlobalProperty;
@@ -84,97 +86,56 @@ public final class OpenmrsConstants {
8486
.getSpecificationVersion() : (getBuildVersionShort() != null ? getBuildVersionShort() : getVersion());
8587

8688
/**
87-
* @return build version with alpha characters (eg:1.10.0 SNAPSHOT Build 24858)
88-
* defined in MANIFEST.MF(specification-Vendor)
89+
* @return build version in the long format (eg:1.10.0 SNAPSHOT Build 24858)
8990
*
9091
* @see #OPENMRS_VERSION_SHORT
9192
* @see #OPENMRS_VERSION
9293
*/
9394
private static String getBuildVersion() {
94-
95-
Properties props = new Properties();
96-
97-
java.net.URL url = OpenmrsConstants.class.getClassLoader().getResource("META-INF/MANIFEST.MF");
98-
99-
if (url == null) {
100-
log.error("Unable to find MANIFEST.MF file built by maven");
101-
return null;
102-
}
103-
104-
// Load the file
105-
try {
106-
props.load(url.openStream());
107-
108-
return props.getProperty("Specification-Vendor");
109-
}
110-
catch (IOException e) {
111-
log.error("Unable to get MANIFEST.MF file into manifest object");
112-
}
113-
114-
return null;
95+
return getOpenmrsProperty("openmrs.version.long");
11596
}
11697

11798
/**
118-
* @return build version without alpha characters (eg: 1.10.0.24858)
119-
* defined in MANIFEST.MF (specification-Version)
99+
* @return build version in the short format (eg: 1.10.0-24858)
120100
*
121101
* @see #OPENMRS_VERSION_SHORT
122102
* @see #OPENMRS_VERSION
123103
*/
124104
private static String getBuildVersionShort() {
125-
126-
Properties props = new Properties();
127-
128-
java.net.URL url = OpenmrsConstants.class.getClassLoader().getResource("META-INF/MANIFEST.MF");
129-
130-
if (url == null) {
131-
log.error("Unable to find MANIFEST.MF file built by maven");
132-
return null;
133-
}
134-
135-
// Load the file
136-
try {
137-
props.load(url.openStream());
138-
139-
return props.getProperty("Specification-Version");
140-
}
141-
catch (IOException e) {
142-
log.error("Unable to get MANIFEST.MF file into manifest object");
143-
}
144-
145-
return null;
105+
return getOpenmrsProperty("openmrs.version.short");
146106
}
147107

148108
/**
149-
* Somewhat hacky method to fetch the version from the maven pom.properties file. <br/>
150-
* This method should not be used unless in a dev environment. The preferred way to get the
151-
* version is from the manifest in the api jar file. More detail is included in the properties
152-
* there.
109+
* This method returns the OpenMRS version as specified in pom.xml.
153110
*
154111
* @return version number defined in maven pom.xml file(s)
155112
* @see #OPENMRS_VERSION_SHORT
156113
* @see #OPENMRS_VERSION
157114
*/
158-
159115
private static String getVersion() {
160-
Properties props = new Properties();
161-
162-
// Get hold of the path to the properties file
163-
// (Maven will make sure it's on the class path)
164-
java.net.URL url = OpenmrsConstants.class.getClassLoader().getResource(
165-
"META-INF/maven/org.openmrs.api/openmrs-api/pom.properties");
166-
if (url == null) {
167-
log.error("Unable to find pom.properties file built by maven");
116+
return getOpenmrsProperty("openmrs.version");
117+
}
118+
119+
public static String getOpenmrsProperty(String property) {
120+
InputStream file = OpenmrsConstants.class.getClassLoader().getResourceAsStream("org/openmrs/api/openmrs.properties");
121+
if (file == null) {
122+
log.error("Unable to find the openmrs.properties file");
168123
return null;
169124
}
170125

171-
// Load the file
172126
try {
173-
props.load(url.openStream());
174-
return props.getProperty("version"); // this will return something like "1.9.0-SNAPSHOT" in dev environments
127+
Properties props = new Properties();
128+
props.load(file);
129+
130+
file.close();
131+
132+
return props.getProperty(property);
175133
}
176134
catch (IOException e) {
177-
log.error("Unable to get pom.properties file into Properties object");
135+
log.error("Unable to parse the openmrs.properties file", e);
136+
}
137+
finally {
138+
IOUtils.closeQuietly(file);
178139
}
179140

180141
return null;

‎api/src/main/resources/applicationContext-service.xml

+1
Original file line numberDiff line numberDiff line change
@@ -781,6 +781,7 @@
781781
<context:component-scan base-package="org.openmrs">
782782
<context:include-filter type="annotation" expression="org.openmrs.annotation.Handler"/>
783783
<context:exclude-filter type="custom" expression="org.openmrs.util.TestTypeFilter"/> <!-- Excludes classes with unit test super classes -->
784+
<context:exclude-filter type="custom" expression="org.openmrs.annotation.OpenmrsProfileExclusionFilter" />
784785
</context:component-scan>
785786

786787
</beans>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
openmrs.version=${openmrs.version}
2+
openmrs.version.short=${openmrs.version.short}
3+
openmrs.version.shortnumericonly=${openmrs.version.shortnumericonly}
4+
openmrs.version.long=${openmrs.version.long}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/**
2+
* The contents of this file are subject to the OpenMRS Public License
3+
* Version 1.0 (the "License"); you may not use this file except in
4+
* compliance with the License. You may obtain a copy of the License at
5+
* http://license.openmrs.org
6+
*
7+
* Software distributed under the License is distributed on an "AS IS"
8+
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
9+
* License for the specific language governing rights and limitations
10+
* under the License.
11+
*
12+
* Copyright (C) OpenMRS, LLC. All Rights Reserved.
13+
*/
14+
package org.openmrs.annotation;
15+
16+
import org.springframework.stereotype.Component;
17+
18+
/**
19+
* Test bean which should be loaded on OpenMRS 1.10 and later
20+
*/
21+
@Component
22+
@OpenmrsProfile(openmrsVersion = "1.10")
23+
public class OpenmrsProfile1_10 {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/**
2+
* The contents of this file are subject to the OpenMRS Public License
3+
* Version 1.0 (the "License"); you may not use this file except in
4+
* compliance with the License. You may obtain a copy of the License at
5+
* http://license.openmrs.org
6+
*
7+
* Software distributed under the License is distributed on an "AS IS"
8+
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
9+
* License for the specific language governing rights and limitations
10+
* under the License.
11+
*
12+
* Copyright (C) OpenMRS, LLC. All Rights Reserved.
13+
*/
14+
package org.openmrs.annotation;
15+
16+
import org.springframework.stereotype.Component;
17+
18+
/**
19+
* Test bean which should be only loaded when running on OpenMRS from 1.6 to 1.8.
20+
*/
21+
@Component
22+
@OpenmrsProfile(openmrsVersion = "[1.6.* - 1.8.*]")
23+
public class OpenmrsProfile1_6To1_8 {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/**
2+
* The contents of this file are subject to the OpenMRS Public License
3+
* Version 1.0 (the "License"); you may not use this file except in
4+
* compliance with the License. You may obtain a copy of the License at
5+
* http://license.openmrs.org
6+
*
7+
* Software distributed under the License is distributed on an "AS IS"
8+
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
9+
* License for the specific language governing rights and limitations
10+
* under the License.
11+
*
12+
* Copyright (C) OpenMRS, LLC. All Rights Reserved.
13+
*/
14+
package org.openmrs.annotation;
15+
16+
import org.springframework.stereotype.Component;
17+
18+
/**
19+
* Test bean which should be only loaded when running on OpenMRS 1.9 and later with the htmlformentry module
20+
*/
21+
@Component
22+
@OpenmrsProfile(openmrsVersion = "1.9", modules = { "htmlformentry:2.3" })
23+
public class OpenmrsProfile1_9WithHtmlformentry {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/**
2+
* The contents of this file are subject to the OpenMRS Public License
3+
* Version 1.0 (the "License"); you may not use this file except in
4+
* compliance with the License. You may obtain a copy of the License at
5+
* http://license.openmrs.org
6+
*
7+
* Software distributed under the License is distributed on an "AS IS"
8+
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
9+
* License for the specific language governing rights and limitations
10+
* under the License.
11+
*
12+
* Copyright (C) OpenMRS, LLC. All Rights Reserved.
13+
*/
14+
package org.openmrs.annotation;
15+
16+
import static org.hamcrest.Matchers.is;
17+
import static org.hamcrest.Matchers.notNullValue;
18+
import static org.junit.Assert.assertThat;
19+
20+
import org.junit.Test;
21+
import org.openmrs.test.BaseContextSensitiveTest;
22+
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
23+
24+
public class OpenmrsProfileExclusionFilterTest extends BaseContextSensitiveTest {
25+
26+
/**
27+
* @verifies not include bean for openmrs from 1_6 to 1_8
28+
* @see OpenmrsProfileExclusionFilter#match(org.springframework.core.type.classreading.MetadataReader, org.springframework.core.type.classreading.MetadataReaderFactory)
29+
*/
30+
@Test(expected = NoSuchBeanDefinitionException.class)
31+
public void match_shouldNotIncludeBeanForOpenmrsFrom1_6To1_8() throws Exception {
32+
applicationContext.getBean(OpenmrsProfile1_6To1_8.class);
33+
}
34+
35+
/**
36+
* @verifies not include bean for openmrs 1_10 and later
37+
* @see OpenmrsProfileExclusionFilter#match(org.springframework.core.type.classreading.MetadataReader, org.springframework.core.type.classreading.MetadataReaderFactory)
38+
*/
39+
@Test(expected = NoSuchBeanDefinitionException.class)
40+
public void match_shouldNotIncludeBeanForOpenmrs1_10AndLater() throws Exception {
41+
applicationContext.getBean(OpenmrsProfile1_10.class);
42+
}
43+
44+
/**
45+
* @verifies include bean for openmrs 1_9 and later
46+
* @see OpenmrsProfileExclusionFilter#match(org.springframework.core.type.classreading.MetadataReader, org.springframework.core.type.classreading.MetadataReaderFactory)
47+
*/
48+
@Test
49+
public void match_shouldIncludeBeanForOpenmrs1_9AndLater() throws Exception {
50+
OpenmrsProfile1_9 bean = applicationContext.getBean(OpenmrsProfile1_9.class);
51+
52+
assertThat(bean, is(notNullValue()));
53+
}
54+
55+
/**
56+
* @verifies not include bean for openmrs 1_9 and later if module missing
57+
* @see OpenmrsProfileExclusionFilter#match(org.springframework.core.type.classreading.MetadataReader, org.springframework.core.type.classreading.MetadataReaderFactory)
58+
*/
59+
@Test(expected = NoSuchBeanDefinitionException.class)
60+
public void match_shouldNotIncludeBeanForOpenmrs1_9AndLaterIfModuleMissing() throws Exception {
61+
applicationContext.getBean(OpenmrsProfile1_9WithHtmlformentry.class);
62+
}
63+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
/**
2+
* The contents of this file are subject to the OpenMRS Public License
3+
* Version 1.0 (the "License"); you may not use this file except in
4+
* compliance with the License. You may obtain a copy of the License at
5+
* http://license.openmrs.org
6+
*
7+
* Software distributed under the License is distributed on an "AS IS"
8+
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
9+
* License for the specific language governing rights and limitations
10+
* under the License.
11+
*
12+
* Copyright (C) OpenMRS, LLC. All Rights Reserved.
13+
*/
14+
package org.openmrs.module;
15+
16+
import org.junit.Assert;
17+
import org.junit.Before;
18+
import org.junit.Test;
19+
import org.openmrs.test.BaseContextSensitiveTest;
20+
21+
import java.net.URI;
22+
import java.util.HashMap;
23+
import java.util.Map;
24+
25+
import static org.hamcrest.Matchers.is;
26+
import static org.junit.Assert.assertThat;
27+
28+
public class ModuleClassLoaderTest extends BaseContextSensitiveTest {
29+
30+
Module mockModule;
31+
32+
Map<String, String> mockModules;
33+
34+
@Before
35+
public void before() {
36+
mockModule = new Module("mockmodule", "mockmodule", "org.openmrs.module.mockmodule", "author", "description", "1.0");
37+
mockModules = new HashMap<String, String>();
38+
}
39+
40+
/**
41+
* @verifies return true if file matches and openmrs version matches
42+
* @see ModuleClassLoader#shouldResourceBeIncluded(Module, java.net.URL, String, java.util.Map)
43+
*/
44+
@Test
45+
public void shouldResourceBeIncluded_shouldReturnTrueIfFileMatchesAndOpenmrsVersionMatches() throws Exception {
46+
ModuleConditionalResource resource = new ModuleConditionalResource();
47+
resource.setPath("lib/mockmodule-api-1.10.jar");
48+
resource.setOpenmrsVersion("1.7-1.8,1.10-1.11");
49+
50+
mockModule.getConditionalResources().add(resource);
51+
52+
boolean result = ModuleClassLoader.shouldResourceBeIncluded(mockModule, URI.create(
53+
"file://module/mockmodule/lib/mockmodule-api-1.10.jar").toURL(), "1.10.0-SNAPSHOT", mockModules);
54+
55+
assertThat(result, is(true));
56+
}
57+
58+
/**
59+
* @verifies return false if file matches but openmrs version does not
60+
* @see ModuleClassLoader#shouldResourceBeIncluded(Module, java.net.URL, String, java.util.Map)
61+
*/
62+
@Test
63+
public void shouldResourceBeIncluded_shouldReturnFalseIfFileMatchesButOpenmrsVersionDoesNot() throws Exception {
64+
ModuleConditionalResource resource = new ModuleConditionalResource();
65+
resource.setPath("lib/mockmodule-api-1.10.jar");
66+
resource.setOpenmrsVersion("1.7-1.8, 1.10-1.11");
67+
68+
mockModule.getConditionalResources().add(resource);
69+
70+
boolean result = ModuleClassLoader.shouldResourceBeIncluded(mockModule, URI.create(
71+
"file://module/mockmodule/lib/mockmodule-api-1.10.jar").toURL(), "1.12.0-SNAPSHOT", mockModules);
72+
73+
assertThat(result, is(false));
74+
}
75+
76+
/**
77+
* @verifies return true if file does not match and openmrs version does not match
78+
* @see ModuleClassLoader#shouldResourceBeIncluded(Module, java.net.URL, String, java.util.Map)
79+
*/
80+
@Test
81+
public void shouldResourceBeIncluded_shouldReturnTrueIfFileDoesNotMatchAndOpenmrsVersionDoesNotMatch() throws Exception {
82+
ModuleConditionalResource resource = new ModuleConditionalResource();
83+
resource.setPath("lib/mockmodule-api.jar");
84+
resource.setOpenmrsVersion("1.10-1.11");
85+
86+
mockModule.getConditionalResources().add(resource);
87+
88+
boolean result = ModuleClassLoader.shouldResourceBeIncluded(mockModule, URI.create(
89+
"file://module/mockmodule/lib/mockmodule-api-1.10.jar").toURL(), "1.9.8-SNAPSHOT", mockModules);
90+
91+
assertThat(result, is(true));
92+
}
93+
94+
/**
95+
* @verifies return true if file matches and module version matches
96+
* @see ModuleClassLoader#shouldResourceBeIncluded(Module, java.net.URL, String, java.util.Map)
97+
*/
98+
@Test
99+
public void shouldResourceBeIncluded_shouldReturnTrueIfFileMatchesAndModuleVersionMatches() throws Exception {
100+
ModuleConditionalResource resource = new ModuleConditionalResource();
101+
resource.setPath("lib/mockmodule-api-1.10.jar");
102+
103+
ModuleConditionalResource.ModuleAndVersion module = new ModuleConditionalResource.ModuleAndVersion();
104+
module.setModuleId("module");
105+
module.setVersion("3.0-4.0,1.0-2.0");
106+
resource.getModules().add(module);
107+
108+
mockModule.getConditionalResources().add(resource);
109+
110+
mockModules.put("module", "1.1");
111+
112+
boolean result = ModuleClassLoader.shouldResourceBeIncluded(mockModule, URI.create(
113+
"file://module/mockmodule/lib/mockmodule-api-1.10.jar").toURL(), "1.10.0-SNAPSHOT", mockModules);
114+
115+
assertThat(result, is(true));
116+
}
117+
118+
/**
119+
* @verifies return false if file matches and module version does not match
120+
* @see ModuleClassLoader#shouldResourceBeIncluded(Module, java.net.URL, String, java.util.Map)
121+
*/
122+
@Test
123+
public void shouldResourceBeIncluded_shouldReturnFalseIfFileMatchesAndModuleVersionDoesNotMatch() throws Exception {
124+
ModuleConditionalResource resource = new ModuleConditionalResource();
125+
resource.setPath("lib/mockmodule-api-1.10.jar");
126+
127+
ModuleConditionalResource.ModuleAndVersion module = new ModuleConditionalResource.ModuleAndVersion();
128+
module.setModuleId("module");
129+
module.setVersion("1.0-2.0");
130+
resource.getModules().add(module);
131+
132+
mockModule.getConditionalResources().add(resource);
133+
134+
mockModules.put("module", "3.0");
135+
136+
boolean result = ModuleClassLoader.shouldResourceBeIncluded(mockModule, URI.create(
137+
"file://module/mockmodule/lib/mockmodule-api-1.10.jar").toURL(), "1.10.0-SNAPSHOT", mockModules);
138+
139+
assertThat(result, is(false));
140+
}
141+
142+
/**
143+
* @verifies return false if file matches and openmrs version matches but module version does not match
144+
* @see ModuleClassLoader#shouldResourceBeIncluded(Module, java.net.URL, String, java.util.Map)
145+
*/
146+
@Test
147+
public void shouldResourceBeIncluded_shouldReturnFalseIfFileMatchesAndOpenmrsVersionMatchesButModuleVersionDoesNotMatch()
148+
throws Exception {
149+
ModuleConditionalResource resource = new ModuleConditionalResource();
150+
resource.setPath("lib/mockmodule-api-1.10.jar");
151+
resource.setOpenmrsVersion("1.10");
152+
153+
ModuleConditionalResource.ModuleAndVersion module = new ModuleConditionalResource.ModuleAndVersion();
154+
module.setModuleId("module");
155+
module.setVersion("1.0-2.0,4.0");
156+
resource.getModules().add(module);
157+
158+
mockModule.getConditionalResources().add(resource);
159+
160+
mockModules.put("module", "3.0");
161+
162+
boolean result = ModuleClassLoader.shouldResourceBeIncluded(mockModule, URI.create(
163+
"file://module/mockmodule/lib/mockmodule-api-1.10.jar").toURL(), "1.10.0-SNAPSHOT", mockModules);
164+
165+
assertThat(result, is(false));
166+
}
167+
168+
/**
169+
* @verifies return false if file matches and module not found
170+
* @see ModuleClassLoader#shouldResourceBeIncluded(Module, java.net.URL, String, java.util.Map)
171+
*/
172+
@Test
173+
public void shouldResourceBeIncluded_shouldReturnFalseIfFileMatchesAndModuleNotFound() throws Exception {
174+
ModuleConditionalResource resource = new ModuleConditionalResource();
175+
resource.setPath("lib/mockmodule-api-1.10.jar");
176+
177+
ModuleConditionalResource.ModuleAndVersion module = new ModuleConditionalResource.ModuleAndVersion();
178+
module.setModuleId("module");
179+
module.setVersion("1.0-2.0");
180+
resource.getModules().add(module);
181+
182+
mockModule.getConditionalResources().add(resource);
183+
184+
mockModules.put("differentModule", "1.0");
185+
186+
boolean result = ModuleClassLoader.shouldResourceBeIncluded(mockModule, URI.create(
187+
"file://module/mockmodule/lib/mockmodule-api-1.10.jar").toURL(), "1.10.0-SNAPSHOT", mockModules);
188+
189+
assertThat(result, is(false));
190+
}
191+
192+
/**
193+
* @verifies return true if file does not match and module version does not match
194+
* @see ModuleClassLoader#shouldResourceBeIncluded(Module, java.net.URL, String, java.util.Map)
195+
*/
196+
@Test
197+
public void shouldResourceBeIncluded_shouldReturnTrueIfFileDoesNotMatchAndModuleVersionDoesNotMatch() throws Exception {
198+
ModuleConditionalResource resource = new ModuleConditionalResource();
199+
resource.setPath("lib/mockmodule-api.jar");
200+
201+
ModuleConditionalResource.ModuleAndVersion module = new ModuleConditionalResource.ModuleAndVersion();
202+
module.setModuleId("module");
203+
module.setVersion("1.0-2.0");
204+
resource.getModules().add(module);
205+
206+
mockModule.getConditionalResources().add(resource);
207+
208+
mockModules.put("module", "3.0");
209+
210+
boolean result = ModuleClassLoader.shouldResourceBeIncluded(mockModule, URI.create(
211+
"file://module/mockmodule/lib/mockmodule-api-1.10.jar").toURL(), "1.10.0-SNAPSHOT", mockModules);
212+
213+
assertThat(result, is(true));
214+
}
215+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
/**
2+
* The contents of this file are subject to the OpenMRS Public License
3+
* Version 1.0 (the "License"); you may not use this file except in
4+
* compliance with the License. You may obtain a copy of the License at
5+
* http://license.openmrs.org
6+
*
7+
* Software distributed under the License is distributed on an "AS IS"
8+
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
9+
* License for the specific language governing rights and limitations
10+
* under the License.
11+
*
12+
* Copyright (C) OpenMRS, LLC. All Rights Reserved.
13+
*/
14+
package org.openmrs.module;
15+
16+
import org.junit.Assert;
17+
import org.junit.Test;
18+
import org.w3c.dom.Document;
19+
import org.w3c.dom.Element;
20+
import org.xml.sax.SAXException;
21+
22+
import javax.xml.parsers.DocumentBuilder;
23+
import javax.xml.parsers.DocumentBuilderFactory;
24+
import javax.xml.parsers.ParserConfigurationException;
25+
import java.io.ByteArrayInputStream;
26+
import java.io.IOException;
27+
import java.util.Arrays;
28+
import java.util.List;
29+
30+
import static org.hamcrest.Matchers.contains;
31+
import static org.hamcrest.Matchers.hasSize;
32+
import static org.junit.Assert.assertThat;
33+
34+
/**
35+
* Tests ModuleFileParser
36+
*/
37+
public class ModuleFileParserTest {
38+
39+
/**
40+
* @verifies parse openmrsVersion and modules
41+
* @see ModuleFileParser#getConditionalResources(org.w3c.dom.Element)
42+
*/
43+
@Test
44+
public void getConditionalResources_shouldParseOpenmrsVersionAndModules() throws Exception {
45+
String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><module configVersion=\"1.2\">"
46+
+ "<conditionalResources><conditionalResource>"
47+
+ "<path>/lib/htmlformentry-api-1.10*</path><openmrsVersion>1.10</openmrsVersion>"
48+
+ "</conditionalResource><conditionalResource>"
49+
+ "<path>/lib/metadatasharing-api-1.9*</path><openmrsVersion>1.9</openmrsVersion>"
50+
+ "<modules><module><moduleId>metadatamapping</moduleId><version>1.0</version></module>"
51+
+ "<module><moduleId>reporting</moduleId><version>2.0</version></module>"
52+
+ "</modules></conditionalResource></conditionalResources></module>";
53+
Element documentElement = getRootElement(xml);
54+
55+
ModuleFileParser moduleFileParser = new ModuleFileParser();
56+
List<ModuleConditionalResource> conditionalResources = moduleFileParser.getConditionalResources(documentElement);
57+
58+
ModuleConditionalResource htmlformentry = new ModuleConditionalResource();
59+
htmlformentry.setPath("/lib/htmlformentry-api-1.10*");
60+
htmlformentry.setOpenmrsVersion("1.10");
61+
62+
ModuleConditionalResource metadatasharing = new ModuleConditionalResource();
63+
metadatasharing.setPath("/lib/metadatasharing-api-1.9*");
64+
metadatasharing.setOpenmrsVersion("1.9");
65+
ModuleConditionalResource.ModuleAndVersion metadatamapping = new ModuleConditionalResource.ModuleAndVersion();
66+
metadatamapping.setModuleId("metadatamapping");
67+
metadatamapping.setVersion("1.0");
68+
ModuleConditionalResource.ModuleAndVersion reporting = new ModuleConditionalResource.ModuleAndVersion();
69+
reporting.setModuleId("reporting");
70+
reporting.setVersion("2.0");
71+
72+
metadatasharing.setModules(Arrays.asList(metadatamapping, reporting));
73+
74+
assertThat(conditionalResources, contains(htmlformentry, metadatasharing));
75+
}
76+
77+
private Element getRootElement(String xml) throws ParserConfigurationException, SAXException, IOException {
78+
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
79+
DocumentBuilder db = dbf.newDocumentBuilder();
80+
Document document = db.parse(new ByteArrayInputStream(xml.getBytes()));
81+
return document.getDocumentElement();
82+
}
83+
84+
/**
85+
* @verifies throw exception if multiple conditionalResources tags found
86+
* @see ModuleFileParser#getConditionalResources(org.w3c.dom.Element)
87+
*/
88+
@Test(expected = IllegalArgumentException.class)
89+
public void getConditionalResources_shouldThrowExceptionIfMultipleConditionalResourcesTagsFound() throws Exception {
90+
String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><module configVersion=\"1.2\">"
91+
+ "<conditionalResources></conditionalResources><conditionalResources></conditionalResources></module>";
92+
Element documentElement = getRootElement(xml);
93+
94+
new ModuleFileParser().getConditionalResources(documentElement);
95+
}
96+
97+
/**
98+
* @verifies throw exception if conditionalResources contains invalid tag
99+
* @see ModuleFileParser#getConditionalResources(org.w3c.dom.Element)
100+
*/
101+
@Test(expected = IllegalArgumentException.class)
102+
public void getConditionalResources_shouldThrowExceptionIfConditionalResourcesContainsInvalidTag() throws Exception {
103+
String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><module configVersion=\"1.2\">"
104+
+ "<conditionalResources><invalidTag></invalidTag></conditionalResources></module>";
105+
Element documentElement = getRootElement(xml);
106+
107+
new ModuleFileParser().getConditionalResources(documentElement);
108+
}
109+
110+
/**
111+
* @verifies throw exception if path is blank
112+
* @see ModuleFileParser#getConditionalResources(org.w3c.dom.Element)
113+
*/
114+
@Test(expected = IllegalArgumentException.class)
115+
public void getConditionalResources_shouldThrowExceptionIfPathIsBlank() throws Exception {
116+
String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><module configVersion=\"1.2\">"
117+
+ "<conditionalResources><conditionalResource>" + "<path></path><openmrsVersion>1.10</openmrsVersion>"
118+
+ "</conditionalResource>></conditionalResources></module>";
119+
Element documentElement = getRootElement(xml);
120+
121+
new ModuleFileParser().getConditionalResources(documentElement);
122+
}
123+
124+
/**
125+
* @verifies parse conditionalResource with whitespace
126+
* @see ModuleFileParser#getConditionalResources(org.w3c.dom.Element)
127+
*/
128+
@Test
129+
public void getConditionalResources_shouldParseConditionalResourceWithWhitespace() throws Exception {
130+
String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><module configVersion=\"1.2\">"
131+
+ "<conditionalResources> <conditionalResource> "
132+
+ "<path>/lib/htmlformentry-api-1.10*</path><openmrsVersion>1.10</openmrsVersion>"
133+
+ "</conditionalResource></conditionalResources></module>";
134+
Element documentElement = getRootElement(xml);
135+
136+
ModuleFileParser moduleFileParser = new ModuleFileParser();
137+
List<ModuleConditionalResource> conditionalResources = moduleFileParser.getConditionalResources(documentElement);
138+
139+
ModuleConditionalResource htmlformentry = new ModuleConditionalResource();
140+
htmlformentry.setPath("/lib/htmlformentry-api-1.10*");
141+
htmlformentry.setOpenmrsVersion("1.10");
142+
143+
assertThat(conditionalResources, contains(htmlformentry));
144+
}
145+
}

‎pom.xml

+3-2
Original file line numberDiff line numberDiff line change
@@ -1020,8 +1020,9 @@
10201020
<TIMESTAMP>${maven.build.timestamp}</TIMESTAMP>
10211021

10221022
<openmrs.version.long>${parsedVersion.majorVersion}.${parsedVersion.minorVersion}.${parsedVersion.incrementalVersion} ${parsedVersion.qualifier} Build ${revisionNumber}</openmrs.version.long>
1023-
<openmrs.version.short>${parsedVersion.majorVersion}.${parsedVersion.minorVersion}.${parsedVersion.incrementalVersion}.${revisionNumber}-${parsedVersion.qualifier}</openmrs.version.short>
1024-
<openmrs.version.shortnumericonly>${parsedVersion.majorVersion}.${parsedVersion.minorVersion}.${parsedVersion.incrementalVersion}.${revisionNumber}</openmrs.version.shortnumericonly>
1023+
<openmrs.version.short>${parsedVersion.majorVersion}.${parsedVersion.minorVersion}.${parsedVersion.incrementalVersion}-${revisionNumber}</openmrs.version.short>
1024+
<openmrs.version.shortnumericonly>${parsedVersion.majorVersion}.${parsedVersion.minorVersion}.${parsedVersion.incrementalVersion}</openmrs.version.shortnumericonly>
1025+
<openmrs.version>${project.version}</openmrs.version>
10251026

10261027
<springVersion>3.0.5.RELEASE</springVersion>
10271028
<customArgLineForTesting />

‎webapp/src/main/webapp/WEB-INF/messages.properties

+1-1
Original file line numberDiff line numberDiff line change
@@ -1925,7 +1925,7 @@ Module.notice=NOTE: Adding, removing, or starting modules will restart OpenMRS,
19251925
Module.locked=Cannot be stopped or unloaded
19261926
Module.locked.help=This module cannot be modified because either it is marked as a mandatory module, is an openmrs core module, or the module_web_admin runtime property is not set to true
19271927
Module.UploadAnUpdate=Upload Updated Module
1928-
Module.requireVersion.outOfBounds=Module requires version between {0} and {1}, Current code version is {2}
1928+
Module.requireVersion.outOfBounds=Module requires version matching {0}. Current code version is {1}
19291929
Module.requireVersion.belowLowerBound=Module requires at least version {0}, Current code version is only {1}
19301930
Module.error.fileCannotBeNull=Module file cannot be null
19311931
Module.error.invalidFileExtension=Module file does not have the correct '.omod' file extension

0 commit comments

Comments
 (0)
Please sign in to comment.