Skip to content

Commit

Permalink
TRUNK-3644 Define which resources to load for module based on OpenMRS…
Browse files Browse the repository at this point in the history
… version

(cherry picked from commit ee0d7ca)
  • Loading branch information
rkorytkowski committed Feb 7, 2014
1 parent 4937ee3 commit e862971
Show file tree
Hide file tree
Showing 20 changed files with 1,076 additions and 121 deletions.
16 changes: 16 additions & 0 deletions api/pom.xml
Expand Up @@ -155,6 +155,22 @@
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
<includes>
<include>org/openmrs/api/openmrs.properties</include>
</includes>
</resource>
<resource>
<directory>src/main/resources</directory>
<filtering>false</filtering>
<excludes>
<exclude>org/openmrs/api/openmrs.properties</exclude>
</excludes>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
Expand Down
37 changes: 37 additions & 0 deletions api/src/main/java/org/openmrs/annotation/OpenmrsProfile.java
@@ -0,0 +1,37 @@
/**
* The contents of this file are subject to the OpenMRS Public License
* Version 1.0 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://license.openmrs.org
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
* License for the specific language governing rights and limitations
* under the License.
*
* Copyright (C) OpenMRS, LLC. All Rights Reserved.
*/
package org.openmrs.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Placed on beans which should be loaded conditionally based on OpenMRS version or started modules.
*
* @since 1.10, 1.9.8, 1.8.5, 1.7.5
*/
@Target( { ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface OpenmrsProfile {

public String openmrsVersion() default "";

public String[] modules() default {};
}
@@ -0,0 +1,82 @@
/**
* The contents of this file are subject to the OpenMRS Public License
* Version 1.0 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://license.openmrs.org
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
* License for the specific language governing rights and limitations
* under the License.
*
* Copyright (C) OpenMRS, LLC. All Rights Reserved.
*/
package org.openmrs.annotation;

import org.apache.commons.lang.StringUtils;
import org.openmrs.module.Module;
import org.openmrs.module.ModuleFactory;
import org.openmrs.module.ModuleUtil;
import org.openmrs.util.OpenmrsConstants;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.TypeFilter;

import java.io.IOException;
import java.util.Map;

/**
* Creates a bean only if profile is matched. It returns true if a bean should be excluded.
*/
public class OpenmrsProfileExclusionFilter implements TypeFilter {

/**
* @param metadataReader
* @param metadataReaderFactory
* @return
* @throws IOException
*
* @should not include bean for openmrs from 1_6 to 1_7
* @should not include bean for openmrs 1_10 and later
* @should not include bean for openmrs 1_8 and later if module missing
* @should include bean for openmrs 1_8 and later
*/
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
Map<String, Object> openmrsProfile = metadataReader.getAnnotationMetadata().getAnnotationAttributes(
"org.openmrs.annotation.OpenmrsProfile");
if (openmrsProfile != null) {
Object openmrsVersion = openmrsProfile.get("openmrsVersion");
if (StringUtils.isNotBlank((String) openmrsVersion)) {
if (!ModuleUtil.matchRequiredVersions(OpenmrsConstants.OPENMRS_VERSION_SHORT, (String) openmrsVersion)) {
return true; //exclude
}
}

String[] modules = (String[]) openmrsProfile.get("modules");

for (String moduleAndVersion : modules) {
boolean exclude = true;

String[] splitModuleAndVersion = moduleAndVersion.split(":");
String moduleId = splitModuleAndVersion[0];
String moduleVersion = splitModuleAndVersion[1];

for (Module module : ModuleFactory.getStartedModules()) {
if (module.getModuleId().equals(moduleId)) {
if (ModuleUtil.matchRequiredVersions(module.getVersion(), moduleVersion)) {
exclude = false;
break;
}
}
}

if (exclude) {
return exclude;
}
}
}

return false; //include
}
}
10 changes: 10 additions & 0 deletions api/src/main/java/org/openmrs/module/Module.java
Expand Up @@ -89,6 +89,8 @@ public final class Module {

private boolean mandatory = Boolean.FALSE;

private List<ModuleConditionalResource> conditionalResources = new ArrayList<ModuleConditionalResource>();

// keep a reference to the file that we got this module from so we can delete
// it if necessary
private File file = null;
Expand Down Expand Up @@ -704,4 +706,12 @@ public void disposeAdvicePointsClassInstance() {
advicePoint.disposeClassInstance();
}
}

public List<ModuleConditionalResource> getConditionalResources() {
return conditionalResources;
}

public void setConditionalResources(List<ModuleConditionalResource> conditionalResources) {
this.conditionalResources = conditionalResources;
}
}
100 changes: 88 additions & 12 deletions api/src/main/java/org/openmrs/module/ModuleClassLoader.java
Expand Up @@ -30,6 +30,7 @@
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
Expand All @@ -40,10 +41,12 @@
import java.util.WeakHashMap;

import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.openmrs.api.APIException;
import org.openmrs.util.OpenmrsClassLoader;
import org.openmrs.util.OpenmrsConstants;
import org.openmrs.util.OpenmrsUtil;

/**
Expand Down Expand Up @@ -82,7 +85,7 @@ protected ModuleClassLoader(final Module module, final List<URL> urls, final Cla
log.debug("URLs length: " + urls.size());

this.module = module;
collectRequiredModuleImports();
requiredModules = collectRequiredModuleImports(module);
collectFilters();
libraryCache = new WeakHashMap<URL, File>();
}
Expand Down Expand Up @@ -210,12 +213,28 @@ private static List<URL> getUrls(final Module module) {
File libdir = new File(tmpModuleDir, "lib");

if (libdir != null && libdir.exists()) {
Map<String, String> startedRelatedModules = new HashMap<String, String>();
for (Module requiredModule : collectRequiredModuleImports(module)) {
startedRelatedModules.put(requiredModule.getModuleId(), requiredModule.getVersion());
}
// recursively get files
Collection<File> files = (Collection<File>) FileUtils.listFiles(libdir, new String[] { "jar" }, true);
for (File file : files) {
if (log.isDebugEnabled())
log.debug("Adding file to results: " + file.getAbsolutePath());
result.add(ModuleUtil.file2url(file));
URL fileUrl = ModuleUtil.file2url(file);

boolean include = shouldResourceBeIncluded(module, fileUrl, OpenmrsConstants.OPENMRS_VERSION_SHORT,
startedRelatedModules);

if (include) {
if (log.isDebugEnabled()) {
log.debug("Including file in classpath: " + fileUrl);
}
result.add(fileUrl);
} else {
if (log.isDebugEnabled()) {
log.debug("Excluding file from classpath: " + fileUrl);
}
}
}
}
}
Expand All @@ -231,6 +250,63 @@ private static List<URL> getUrls(final Module module) {
return result;
}

/**
* Determines whether or not the given resource should be available on the classpath based on
* OpenMRS version and/or modules' version. It uses the conditionalResources section specified in config.xml.
*
* Resources that are not mentioned as conditional resources are included by default.
*
* All conditions for a conditional resource to be included must match.
*
* @param module
* @param fileUrl
* @return true if it should be included
* @should return true if file matches and openmrs version matches
* @should return false if file matches but openmrs version does not
* @should return true if file does not match and openmrs version does not match
* @should return true if file matches and module version matches
* @should return false if file matches and module version does not match
* @should return false if file matches and openmrs version matches but module version does not match
* @should return false if file matches and module not found
* @should return true if file does not match and module version does not match
*/
static boolean shouldResourceBeIncluded(Module module, URL fileUrl, String openmrsVersion,
Map<String, String> startedRelatedModules) {
boolean include = true; //all resources are included by default

for (ModuleConditionalResource conditionalResource : module.getConditionalResources()) {
if (fileUrl.getPath().matches(".*" + conditionalResource.getPath() + "$")) {
include = false; //if a resource matches a path of contidionalResource then it must meet all conditions

if (StringUtils.isNotBlank(conditionalResource.getOpenmrsVersion())) { //openmrsVersion is optional
include = ModuleUtil.matchRequiredVersions(openmrsVersion, conditionalResource.getOpenmrsVersion());

if (!include) {
return false;
}
}

if (conditionalResource.getModules() != null) { //modules are optional
for (ModuleConditionalResource.ModuleAndVersion conditionalModuleResource : conditionalResource
.getModules()) {
String moduleVersion = startedRelatedModules.get(conditionalModuleResource.getModuleId());
if (moduleVersion != null) {
include = ModuleUtil
.matchRequiredVersions(moduleVersion, conditionalModuleResource.getVersion());

if (!include) {
return false;
}
}
}

}
}
}

return include;
}

/**
* 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 Expand Up @@ -273,32 +349,32 @@ private static List<URL> getUrls(final Module module, final URL[] existingUrls)
* Get and cache the imports for this module. The imports should just be the modules that set as
* "required" by this module
*/
protected void collectRequiredModuleImports() {
protected static Module[] collectRequiredModuleImports(Module module) {
// collect imported modules (exclude duplicates)
Map<String, Module> publicImportsMap = new WeakHashMap<String, Module>(); //<module ID, Module>

for (String moduleId : ModuleConstants.CORE_MODULES.keySet()) {
Module module = ModuleFactory.getModuleById(moduleId);
Module coreModule = ModuleFactory.getModuleById(moduleId);

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

// if this is already the classloader for one of the core modules, don't put it on the import list
if (module != null && !moduleId.equals(this.getModule().getModuleId())) {
publicImportsMap.put(moduleId, module);
if (coreModule != null && !moduleId.equals(module.getModuleId())) {
publicImportsMap.put(moduleId, coreModule);
}
}

for (String requiredPackage : getModule().getRequiredModules()) {
for (String requiredPackage : module.getRequiredModules()) {
Module requiredModule = ModuleFactory.getModuleByPackage(requiredPackage);
if (ModuleFactory.isModuleStarted(requiredModule)) {
publicImportsMap.put(requiredModule.getModuleId(), requiredModule);
}
}
requiredModules = publicImportsMap.values().toArray(new Module[publicImportsMap.size()]);
return publicImportsMap.values().toArray(new Module[publicImportsMap.size()]);

}

Expand Down Expand Up @@ -339,7 +415,7 @@ protected void modulesSetChanged() {
}
log.debug(buf.toString());
}
collectRequiredModuleImports();
requiredModules = collectRequiredModuleImports(getModule());
// repopulate resource URLs
//resourceLoader = ModuleResourceLoader.get(getModule());
collectFilters();
Expand Down

0 comments on commit e862971

Please sign in to comment.