From 80813375ae9eab84118a4905946ab6073d176d71 Mon Sep 17 00:00:00 2001 From: Darren Shepherd Date: Tue, 5 Nov 2013 11:02:41 -0700 Subject: [PATCH] Allow disabling modules and extensions from module.properties Discovery of modules is based on classpath scanning. In some situations it may not be possible or desirable to change the classpath. To force a module to not load you can create a file called modules.properties on the classpath that can exclude specific modules from loading. Additionally this same file can be used to exclude a specific extension. Extension loading is typically done through global configuration. If you want to set up an environment and you don't even want the extension/module loaded on the first start, then using the config file is appropriate. Example: modules.properties modules.exclude=storage-image-s3,storage-volume-solidfire extensions.exclude=ClusterScopeStoragePoolAllocator,ZoneWideStoragePoolAllocator Typically you would want to place this file in /etc/cloudstack/management --- .../lifecycle/registry/ExtensionRegistry.java | 15 +---- .../lifecycle/registry/RegistryLifecycle.java | 46 +++++++++++++- .../lifecycle/registry/RegistryUtils.java | 35 +++++++++++ .../module/factory/QuietLoaderFactory.java | 62 +++++++++++++++++++ .../impl/DefaultModuleDefinitionSet.java | 59 +++++++++++++++++- .../module/model/impl/defaults-context.xml | 9 +++ .../ModuleBasedContextFactoryTest.java | 10 +++ ...asspathModuleDefinitionSetLocatorTest.java | 2 +- .../testhierarchy/excluded/module.properties | 19 ++++++ .../testhierarchy/excluded/test-context.xml | 33 ++++++++++ .../testhierarchy/excluded2/module.properties | 19 ++++++ .../testhierarchy/excluded2/test-context.xml | 33 ++++++++++ .../orphan-of-excluded/defaults.properties | 20 ++++++ .../orphan-of-excluded/module.properties | 19 ++++++ .../orphan-of-excluded/test-context.xml | 33 ++++++++++ 15 files changed, 396 insertions(+), 18 deletions(-) create mode 100644 framework/spring/lifecycle/src/main/java/org/apache/cloudstack/spring/lifecycle/registry/RegistryUtils.java create mode 100644 framework/spring/module/src/main/java/org/apache/cloudstack/spring/module/factory/QuietLoaderFactory.java create mode 100644 framework/spring/module/src/test/resources/testhierarchy/excluded/module.properties create mode 100644 framework/spring/module/src/test/resources/testhierarchy/excluded/test-context.xml create mode 100644 framework/spring/module/src/test/resources/testhierarchy/excluded2/module.properties create mode 100644 framework/spring/module/src/test/resources/testhierarchy/excluded2/test-context.xml create mode 100644 framework/spring/module/src/test/resources/testhierarchy/orphan-of-excluded/defaults.properties create mode 100644 framework/spring/module/src/test/resources/testhierarchy/orphan-of-excluded/module.properties create mode 100644 framework/spring/module/src/test/resources/testhierarchy/orphan-of-excluded/test-context.xml diff --git a/framework/spring/lifecycle/src/main/java/org/apache/cloudstack/spring/lifecycle/registry/ExtensionRegistry.java b/framework/spring/lifecycle/src/main/java/org/apache/cloudstack/spring/lifecycle/registry/ExtensionRegistry.java index 2bd362eb8e3..38008bb76f2 100644 --- a/framework/spring/lifecycle/src/main/java/org/apache/cloudstack/spring/lifecycle/registry/ExtensionRegistry.java +++ b/framework/spring/lifecycle/src/main/java/org/apache/cloudstack/spring/lifecycle/registry/ExtensionRegistry.java @@ -34,7 +34,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.BeanNameAware; -import com.cloud.utils.component.Named; import com.cloud.utils.component.Registry; public class ExtensionRegistry implements Registry, Configurable, BeanNameAware { @@ -81,7 +80,7 @@ public class ExtensionRegistry implements Registry, Configurable, BeanNa } } - String name = getName(item); + String name = RegistryUtils.getName(item); if ( name != null && exclude.size() > 0 && exclude.contains(name) ) { return false; @@ -103,7 +102,7 @@ public class ExtensionRegistry implements Registry, Configurable, BeanNa break; } - if ( getName(registered.get(i)).equals(orderTest) ) { + if ( RegistryUtils.getName(registered.get(i)).equals(orderTest) ) { i++; } } @@ -116,16 +115,6 @@ public class ExtensionRegistry implements Registry, Configurable, BeanNa return true; } - - protected String getName(Object object) { - if ( object instanceof Named ) { - String name = ((Named)object).getName(); - if ( name != null ) - return name; - } - - return object == null ? null : object.getClass().getSimpleName(); - } @Override public void unregister(Object type) { diff --git a/framework/spring/lifecycle/src/main/java/org/apache/cloudstack/spring/lifecycle/registry/RegistryLifecycle.java b/framework/spring/lifecycle/src/main/java/org/apache/cloudstack/spring/lifecycle/registry/RegistryLifecycle.java index bd7a0334a6f..4975c5a4d70 100644 --- a/framework/spring/lifecycle/src/main/java/org/apache/cloudstack/spring/lifecycle/registry/RegistryLifecycle.java +++ b/framework/spring/lifecycle/src/main/java/org/apache/cloudstack/spring/lifecycle/registry/RegistryLifecycle.java @@ -20,6 +20,7 @@ package org.apache.cloudstack.spring.lifecycle.registry; import java.util.HashSet; import java.util.Iterator; +import java.util.Properties; import java.util.Set; import org.slf4j.Logger; @@ -29,6 +30,7 @@ import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.SmartLifecycle; +import org.springframework.util.StringUtils; import com.cloud.utils.component.Registry; @@ -36,6 +38,9 @@ public class RegistryLifecycle implements BeanPostProcessor, SmartLifecycle, App private static final Logger log = LoggerFactory.getLogger(RegistryLifecycle.class); + public static final String EXTENSION_EXCLUDE = "extensions.exclude"; + public static final String EXTENSION_INCLUDE_PREFIX = "extensions.include."; + Registry registry; /* The bean name works around circular dependency issues in Spring. This shouldn't be @@ -46,15 +51,52 @@ public class RegistryLifecycle implements BeanPostProcessor, SmartLifecycle, App Set beans = new HashSet(); Class typeClass; ApplicationContext applicationContext; + Set excludes = null; @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { - if ( typeClass.isAssignableFrom(bean.getClass()) ) + if ( typeClass.isAssignableFrom(bean.getClass()) && ! isExcluded(bean) ) { beans.add(bean); - + } + return bean; } + protected synchronized boolean isExcluded(Object bean) { + String name = RegistryUtils.getName(bean); + + if ( excludes == null ) { + loadExcluded(); + } + + boolean result = excludes.contains(name); + if ( result ) { + log.info("Excluding extension [{}] based on configuration", name); + } + + return result; + } + + protected synchronized void loadExcluded() { + Properties props = applicationContext.getBean("DefaultConfigProperties", Properties.class); + excludes = new HashSet(); + for ( String exclude : props.getProperty(EXTENSION_EXCLUDE, "").trim().split("\\s*,\\s*") ) { + if ( StringUtils.hasText(exclude) ) { + excludes.add(exclude); + } + } + + for ( String key : props.stringPropertyNames() ) { + if ( key.startsWith(EXTENSION_INCLUDE_PREFIX) ) { + String module = key.substring(EXTENSION_INCLUDE_PREFIX.length()); + boolean include = props.getProperty(key).equalsIgnoreCase("true"); + if ( ! include ) { + excludes.add(module); + } + } + } + } + @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { return bean; diff --git a/framework/spring/lifecycle/src/main/java/org/apache/cloudstack/spring/lifecycle/registry/RegistryUtils.java b/framework/spring/lifecycle/src/main/java/org/apache/cloudstack/spring/lifecycle/registry/RegistryUtils.java new file mode 100644 index 00000000000..8d7e9b68fb6 --- /dev/null +++ b/framework/spring/lifecycle/src/main/java/org/apache/cloudstack/spring/lifecycle/registry/RegistryUtils.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.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://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.cloudstack.spring.lifecycle.registry; + +import com.cloud.utils.component.Named; + +public class RegistryUtils { + + public static String getName(Object object) { + if ( object instanceof Named ) { + String name = ((Named)object).getName(); + if ( name != null ) + return name; + } + + return object == null ? null : object.getClass().getSimpleName(); + } + +} diff --git a/framework/spring/module/src/main/java/org/apache/cloudstack/spring/module/factory/QuietLoaderFactory.java b/framework/spring/module/src/main/java/org/apache/cloudstack/spring/module/factory/QuietLoaderFactory.java new file mode 100644 index 00000000000..74fc068fa74 --- /dev/null +++ b/framework/spring/module/src/main/java/org/apache/cloudstack/spring/module/factory/QuietLoaderFactory.java @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.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://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.cloudstack.spring.module.factory; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.beans.factory.FactoryBean; +import org.springframework.core.io.Resource; + +public class QuietLoaderFactory implements FactoryBean { + + Resource[] resources; + + @Override + public Resource[] getObject() throws Exception { + List existing = new ArrayList(); + + for ( Resource resource : resources ) { + if ( resource.exists() ) { + existing.add(resource); + } + } + + return existing.toArray(new Resource[existing.size()]); + } + + @Override + public Class getObjectType() { + return Resource[].class; + } + + @Override + public boolean isSingleton() { + return false; + } + + public Resource[] getResources() { + return resources; + } + + public void setResources(Resource[] resources) { + this.resources = resources; + } + +} diff --git a/framework/spring/module/src/main/java/org/apache/cloudstack/spring/module/model/impl/DefaultModuleDefinitionSet.java b/framework/spring/module/src/main/java/org/apache/cloudstack/spring/module/model/impl/DefaultModuleDefinitionSet.java index 15df839cf1a..3106ee5234a 100644 --- a/framework/spring/module/src/main/java/org/apache/cloudstack/spring/module/model/impl/DefaultModuleDefinitionSet.java +++ b/framework/spring/module/src/main/java/org/apache/cloudstack/spring/module/model/impl/DefaultModuleDefinitionSet.java @@ -19,19 +19,23 @@ package org.apache.cloudstack.spring.module.model.impl; import java.io.IOException; +import java.io.InputStream; import java.net.URL; import java.util.ArrayList; import java.util.EmptyStackException; import java.util.HashMap; +import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; +import java.util.Properties; import java.util.Set; import java.util.Stack; import org.apache.cloudstack.spring.module.context.ResourceApplicationContext; import org.apache.cloudstack.spring.module.model.ModuleDefinition; import org.apache.cloudstack.spring.module.model.ModuleDefinitionSet; +import org.apache.commons.io.IOUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeansException; @@ -40,18 +44,25 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.Resource; import org.springframework.core.io.UrlResource; +import org.springframework.util.StringUtils; public class DefaultModuleDefinitionSet implements ModuleDefinitionSet { private static final Logger log = LoggerFactory.getLogger(DefaultModuleDefinitionSet.class); public static final String DEFAULT_CONFIG_RESOURCES = "DefaultConfigResources"; + public static final String DEFAULT_CONFIG_PROPERTIES = "DefaultConfigProperties"; + public static final String MODULES_EXCLUDE = "modules.exclude"; + public static final String MODULES_INCLUDE_PREFIX = "modules.include."; + public static final String MODULE_PROPERITES = "ModuleProperties"; public static final String DEFAULT_CONFIG_XML = "defaults-context.xml"; String root; Map modules; Map contexts = new HashMap(); ApplicationContext rootContext = null; + Set excludes = new HashSet(); + Properties configProperties = null; public DefaultModuleDefinitionSet(Map modules, String root) { super(); @@ -136,7 +147,7 @@ public class DefaultModuleDefinitionSet implements ModuleDefinitionSet { } protected boolean shouldLoad(ModuleDefinition def) { - return true; + return ! excludes.contains(def.getName()); } protected ApplicationContext getDefaultsContext() { @@ -156,9 +167,52 @@ public class DefaultModuleDefinitionSet implements ModuleDefinitionSet { } } }); - + + configProperties = (Properties) context.getBean(DEFAULT_CONFIG_PROPERTIES); + for ( Resource resource : resources ) { + load(resource, configProperties); + } + + for ( Resource resource : (Resource[])context.getBean(MODULE_PROPERITES) ) { + load(resource, configProperties); + } + + parseExcludes(); + return context; } + + protected void parseExcludes() { + for ( String exclude : configProperties.getProperty(MODULES_EXCLUDE, "").trim().split("\\s*,\\s*") ) { + if ( StringUtils.hasText(exclude) ) { + excludes.add(exclude); + } + } + + for ( String key : configProperties.stringPropertyNames() ) { + if ( key.startsWith(MODULES_INCLUDE_PREFIX) ) { + String module = key.substring(MODULES_INCLUDE_PREFIX.length()); + boolean include = configProperties.getProperty(key).equalsIgnoreCase("true"); + if ( ! include ) { + excludes.add(module); + } + } + } + } + + protected void load(Resource resource, Properties props) { + InputStream is = null; + try { + if ( resource.exists() ) { + is = resource.getInputStream(); + props.load(is); + } + } catch (IOException e) { + throw new IllegalStateException("Failed to load resource [" + resource + "]", e); + } finally { + IOUtils.closeQuietly(is); + } + } protected void printHierarchy() { withModule(new WithModule() { @@ -178,6 +232,7 @@ public class DefaultModuleDefinitionSet implements ModuleDefinitionSet { return; if ( ! shouldLoad(def) ) { + log.info("Excluding context [{}] based on configuration", def.getName()); return; } diff --git a/framework/spring/module/src/main/resources/org/apache/cloudstack/spring/module/model/impl/defaults-context.xml b/framework/spring/module/src/main/resources/org/apache/cloudstack/spring/module/model/impl/defaults-context.xml index b19833a735b..8c133ec5a94 100644 --- a/framework/spring/module/src/main/resources/org/apache/cloudstack/spring/module/model/impl/defaults-context.xml +++ b/framework/spring/module/src/main/resources/org/apache/cloudstack/spring/module/model/impl/defaults-context.xml @@ -24,5 +24,14 @@ http://www.springframework.org/schema/context/spring-context-3.0.xsd"> + + + + + + classpath:modules.properties + + + diff --git a/framework/spring/module/src/test/java/org/apache/cloudstack/spring/module/factory/ModuleBasedContextFactoryTest.java b/framework/spring/module/src/test/java/org/apache/cloudstack/spring/module/factory/ModuleBasedContextFactoryTest.java index 2947615d597..3cb00bcc714 100644 --- a/framework/spring/module/src/test/java/org/apache/cloudstack/spring/module/factory/ModuleBasedContextFactoryTest.java +++ b/framework/spring/module/src/test/java/org/apache/cloudstack/spring/module/factory/ModuleBasedContextFactoryTest.java @@ -66,6 +66,16 @@ public class ModuleBasedContextFactoryTest { assertEquals("a string", set.getApplicationContext("child1").getBean("override", String.class)); } + @Test + public void testExcluded() throws IOException { + ModuleBasedContextFactory factory = new ModuleBasedContextFactory(); + ModuleDefinitionSet set = factory.loadModules(defs, "base"); + + assertNull(set.getApplicationContext("excluded")); + assertNull(set.getApplicationContext("excluded2")); + assertNull(set.getApplicationContext("orphan-of-excluded")); + } + @Test public void testBeans() throws IOException { ModuleBasedContextFactory factory = new ModuleBasedContextFactory(); diff --git a/framework/spring/module/src/test/java/org/apache/cloudstack/spring/module/locator/impl/ClasspathModuleDefinitionSetLocatorTest.java b/framework/spring/module/src/test/java/org/apache/cloudstack/spring/module/locator/impl/ClasspathModuleDefinitionSetLocatorTest.java index 5114187b9a5..989aa9fbcb9 100644 --- a/framework/spring/module/src/test/java/org/apache/cloudstack/spring/module/locator/impl/ClasspathModuleDefinitionSetLocatorTest.java +++ b/framework/spring/module/src/test/java/org/apache/cloudstack/spring/module/locator/impl/ClasspathModuleDefinitionSetLocatorTest.java @@ -34,7 +34,7 @@ public class ClasspathModuleDefinitionSetLocatorTest { Collection modules = factory.locateModules("testhierarchy"); - assertEquals(5, modules.size()); + assertEquals(8, modules.size()); } } diff --git a/framework/spring/module/src/test/resources/testhierarchy/excluded/module.properties b/framework/spring/module/src/test/resources/testhierarchy/excluded/module.properties new file mode 100644 index 00000000000..abd83684710 --- /dev/null +++ b/framework/spring/module/src/test/resources/testhierarchy/excluded/module.properties @@ -0,0 +1,19 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.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://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +name=excluded +parent=base \ No newline at end of file diff --git a/framework/spring/module/src/test/resources/testhierarchy/excluded/test-context.xml b/framework/spring/module/src/test/resources/testhierarchy/excluded/test-context.xml new file mode 100644 index 00000000000..57e68ddd595 --- /dev/null +++ b/framework/spring/module/src/test/resources/testhierarchy/excluded/test-context.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + diff --git a/framework/spring/module/src/test/resources/testhierarchy/excluded2/module.properties b/framework/spring/module/src/test/resources/testhierarchy/excluded2/module.properties new file mode 100644 index 00000000000..2d06f6918f1 --- /dev/null +++ b/framework/spring/module/src/test/resources/testhierarchy/excluded2/module.properties @@ -0,0 +1,19 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.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://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +name=excluded2 +parent=base \ No newline at end of file diff --git a/framework/spring/module/src/test/resources/testhierarchy/excluded2/test-context.xml b/framework/spring/module/src/test/resources/testhierarchy/excluded2/test-context.xml new file mode 100644 index 00000000000..55fd9b4810d --- /dev/null +++ b/framework/spring/module/src/test/resources/testhierarchy/excluded2/test-context.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + diff --git a/framework/spring/module/src/test/resources/testhierarchy/orphan-of-excluded/defaults.properties b/framework/spring/module/src/test/resources/testhierarchy/orphan-of-excluded/defaults.properties new file mode 100644 index 00000000000..b1f6ab97976 --- /dev/null +++ b/framework/spring/module/src/test/resources/testhierarchy/orphan-of-excluded/defaults.properties @@ -0,0 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.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://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +modules.include.excluded=false +modules.include.base=True +modules.exclude=excluded2,excluded2 \ No newline at end of file diff --git a/framework/spring/module/src/test/resources/testhierarchy/orphan-of-excluded/module.properties b/framework/spring/module/src/test/resources/testhierarchy/orphan-of-excluded/module.properties new file mode 100644 index 00000000000..7684deb6c26 --- /dev/null +++ b/framework/spring/module/src/test/resources/testhierarchy/orphan-of-excluded/module.properties @@ -0,0 +1,19 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.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://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +name=orphan-of-excluded +parent=excluded \ No newline at end of file diff --git a/framework/spring/module/src/test/resources/testhierarchy/orphan-of-excluded/test-context.xml b/framework/spring/module/src/test/resources/testhierarchy/orphan-of-excluded/test-context.xml new file mode 100644 index 00000000000..6495e1b48b8 --- /dev/null +++ b/framework/spring/module/src/test/resources/testhierarchy/orphan-of-excluded/test-context.xml @@ -0,0 +1,33 @@ + + + + + + + + + + +