[spring-extensions] branch master updated: IDP-1128 - Ability to inject reloadable beans

Scott Cantor cantor.2 at osu.edu
Wed Feb 22 19:51:23 EST 2017


This is an automated email from the git hooks/post-receive script.

scantor pushed a commit to branch master
in repository spring-extensions.

View the commit online:
http://git.shibboleth.net/view/?p=spring-extensions.git;a=commit;h=963b1bdffe2d348edf115dc5c714d85dd967500c

The following commit(s) were added to refs/heads/master by this push:
       new  963b1bd   IDP-1128 - Ability to inject reloadable beans
963b1bd is described below

commit 963b1bdffe2d348edf115dc5c714d85dd967500c
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Wed Feb 22 19:51:16 2017 -0500

    IDP-1128 - Ability to inject reloadable beans
    
    https://issues.shibboleth.net/jira/browse/IDP-1128
    
    Machinery and insanity to support turning
    ApplicationContexts into reloadable services.
---
 .../ext/spring/config/NonReloadableExcluder.java   |  57 +++++
 .../ext/spring/config/ReloadableScope.java         |  91 +++++++
 .../service/ApplicationContextServiceStrategy.java |  62 +++++
 .../ApplicationContextServiceableComponent.java    |  47 ++++
 .../spring/service/ReloadableSpringService.java    |  61 ++++-
 .../ext/spring/util/ApplicationContextBuilder.java | 261 +++++++++++++++++++++
 .../shibboleth/ext/spring/util/SpringSupport.java  |   4 +-
 .../ext/spring/service/NonReloadableTestBean.java  |  74 ++++++
 .../spring/service/ReloadableBeanServiceTest.java  |  70 ++++++
 .../service/ReloadableSpringServiceTest.java       |  23 +-
 .../ext/spring/service/ReloadableTestBean.java     |  64 +++++
 .../ext/spring/service/ReloadableBeans1.xml        |  51 ++++
 12 files changed, 841 insertions(+), 24 deletions(-)

diff --git a/src/main/java/net/shibboleth/ext/spring/config/NonReloadableExcluder.java b/src/main/java/net/shibboleth/ext/spring/config/NonReloadableExcluder.java
new file mode 100644
index 0000000..48db622
--- /dev/null
+++ b/src/main/java/net/shibboleth/ext/spring/config/NonReloadableExcluder.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID 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 net.shibboleth.ext.spring.config;
+
+import javax.annotation.Nonnull;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
+import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
+
+/**
+ * Post-processes bean definitions by marking any reloadable beans as singletons,
+ * and any non-reloadable beans as lazy-init to limit/prevent instantiation.
+ * 
+ * <p>Used to implement the "reloadable" custom bean scope in concert with {@link ReloadableScope}.</p>
+ * 
+ * @since 5.4.0
+ */
+public class NonReloadableExcluder implements BeanFactoryPostProcessor {
+    
+    /** Logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(NonReloadableExcluder.class);
+
+    /** {@inheritDoc} */
+    public void postProcessBeanFactory(final ConfigurableListableBeanFactory beanFactory) throws BeansException {
+        
+        for (final String beanName : beanFactory.getBeanDefinitionNames()) {
+            final BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName);
+            if (beanDef.getScope() != null && ReloadableScope.SCOPE_RELOADABLE.equals(beanDef.getScope())) {
+                log.debug("Converting reloadable bean '{}' into singleton", beanName);
+                beanDef.setScope(BeanDefinition.SCOPE_SINGLETON);
+            } else {
+                log.debug("Hiding non-reloadable bean '{}' as a lazy-init", beanName);
+                beanDef.setLazyInit(true);
+            }
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/src/main/java/net/shibboleth/ext/spring/config/ReloadableScope.java b/src/main/java/net/shibboleth/ext/spring/config/ReloadableScope.java
new file mode 100644
index 0000000..27259ac
--- /dev/null
+++ b/src/main/java/net/shibboleth/ext/spring/config/ReloadableScope.java
@@ -0,0 +1,91 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID 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 net.shibboleth.ext.spring.config;
+
+import javax.annotation.Nonnull;
+
+import net.shibboleth.utilities.java.support.annotation.ParameterName;
+import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+import net.shibboleth.utilities.java.support.service.ReloadableService;
+import net.shibboleth.utilities.java.support.service.ServiceableComponent;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.ObjectFactory;
+import org.springframework.beans.factory.config.Scope;
+import org.springframework.context.ApplicationContext;
+
+/**
+ * Custom Spring bean {@link Scope} that directs bean requests into a managed {@link ApplicationContext}.
+ * 
+ * @since 5.4.0
+ */
+public class ReloadableScope implements Scope {
+
+    /** Scope indicating reloadability. */
+    @Nonnull @NotEmpty public static final String SCOPE_RELOADABLE = "reloadable";
+    
+    /** Logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(ReloadableScope.class);
+    
+    /** Managed context service wrapper. */
+    @Nonnull private final ReloadableService<ApplicationContext> reloadableService;
+    
+    /**
+     * Constructor.
+     *
+     * @param service instance of Spring context to wrap
+     */
+    public ReloadableScope(
+            @Nonnull @ParameterName(name="service") final ReloadableService<ApplicationContext> service) {
+        reloadableService = Constraint.isNotNull(service, "ReloadableService cannot be null");
+    }
+
+    /** {@inheritDoc} */
+    public Object get(final String name, final ObjectFactory<?> objectFactory) {
+        log.debug("Accessing reloadable bean instance '{}'", name);
+        final ServiceableComponent<ApplicationContext> component = reloadableService.getServiceableComponent();
+        try {
+            return component.getComponent().getBean(name);
+        } finally {
+            component.unpinComponent();
+        }
+    }
+
+    /** {@inheritDoc} */
+    public Object remove(final String name) {
+        throw new UnsupportedOperationException("No support for object removal");
+    }
+
+    /** {@inheritDoc} */
+    public void registerDestructionCallback(final String name, final Runnable callback) {
+        log.warn("Ignoring unsupported destruction callback for '{}'", name);
+    }
+
+    /** {@inheritDoc} */
+    public Object resolveContextualObject(final String key) {
+        return null;
+    }
+
+    /** {@inheritDoc} */
+    public String getConversationId() {
+        return null;
+    }
+
+}
\ No newline at end of file
diff --git a/src/main/java/net/shibboleth/ext/spring/service/ApplicationContextServiceStrategy.java b/src/main/java/net/shibboleth/ext/spring/service/ApplicationContextServiceStrategy.java
new file mode 100644
index 0000000..e983a1b
--- /dev/null
+++ b/src/main/java/net/shibboleth/ext/spring/service/ApplicationContextServiceStrategy.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID 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 net.shibboleth.ext.spring.service;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+import net.shibboleth.utilities.java.support.service.ServiceableComponent;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.context.ApplicationContext;
+
+import com.google.common.base.Function;
+
+/**
+ * Strategy for summoning up an {@link ApplicationContextServiceableComponent} wrapper
+ * around a populated {@link ApplicationContext}.
+ * 
+ * @since 5.4.0
+ */
+public class ApplicationContextServiceStrategy implements
+        Function<ApplicationContext, ServiceableComponent<ApplicationContext>> {
+
+    /** Logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(ApplicationContextServiceStrategy.class);
+    
+    /** {@inheritDoc} */
+    @Override @Nullable public ServiceableComponent<ApplicationContext> apply(
+            @Nullable final ApplicationContext appContext) {
+
+        if (appContext != null) {
+            final ApplicationContextServiceableComponent wrapper = new ApplicationContextServiceableComponent();
+            wrapper.setApplicationContext(appContext);
+            wrapper.setId(appContext.getId());
+            try {
+                wrapper.initialize();
+                return wrapper;
+            } catch (final ComponentInitializationException e) {
+                log.error("Unable to initialize component wrapper for ApplicationContext", e);
+            }
+        }
+        return null;
+    }
+    
+}
\ No newline at end of file
diff --git a/src/main/java/net/shibboleth/ext/spring/service/ApplicationContextServiceableComponent.java b/src/main/java/net/shibboleth/ext/spring/service/ApplicationContextServiceableComponent.java
new file mode 100644
index 0000000..a0b57a5
--- /dev/null
+++ b/src/main/java/net/shibboleth/ext/spring/service/ApplicationContextServiceableComponent.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID 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 net.shibboleth.ext.spring.service;
+
+import javax.annotation.Nonnull;
+
+import org.springframework.context.ApplicationContext;
+
+import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+/**
+ * Wraps a Spring {@link ApplicationContext} so it can itself be exposed as a serviceable component.
+ * 
+ * @since 5.4.0
+ */
+public class ApplicationContextServiceableComponent extends AbstractServiceableComponent<ApplicationContext> {
+    
+    /** {@inheritDoc} */
+    public void setId(@Nonnull @NotEmpty final String componentId) {
+        super.setId(componentId);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    @Nonnull public ApplicationContext getComponent() {
+        final ApplicationContext context = getApplicationContext();
+        Constraint.isNotNull(context, "ApplicationContext not yet set");
+        return context;
+    }
+
+}
\ No newline at end of file
diff --git a/src/main/java/net/shibboleth/ext/spring/service/ReloadableSpringService.java b/src/main/java/net/shibboleth/ext/spring/service/ReloadableSpringService.java
index 20d9eb1..6c3be8b 100644
--- a/src/main/java/net/shibboleth/ext/spring/service/ReloadableSpringService.java
+++ b/src/main/java/net/shibboleth/ext/spring/service/ReloadableSpringService.java
@@ -19,6 +19,7 @@ package net.shibboleth.ext.spring.service;
 
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 
@@ -26,13 +27,14 @@ import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 import javax.annotation.concurrent.ThreadSafe;
 
-import net.shibboleth.ext.spring.util.SpringSupport;
+import net.shibboleth.ext.spring.util.ApplicationContextBuilder;
 import net.shibboleth.utilities.java.support.annotation.ParameterName;
 import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
 import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
 import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
 import net.shibboleth.utilities.java.support.component.ComponentSupport;
 import net.shibboleth.utilities.java.support.logic.Constraint;
+import net.shibboleth.utilities.java.support.primitive.StringSupport;
 import net.shibboleth.utilities.java.support.service.AbstractReloadableService;
 import net.shibboleth.utilities.java.support.service.ServiceException;
 import net.shibboleth.utilities.java.support.service.ServiceableComponent;
@@ -46,9 +48,9 @@ import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
 import org.springframework.beans.factory.config.BeanPostProcessor;
 import org.springframework.context.ApplicationContext;
 import org.springframework.context.ApplicationContextAware;
-import org.springframework.context.ApplicationContextInitializer;
 import org.springframework.context.Lifecycle;
 import org.springframework.context.support.GenericApplicationContext;
+import org.springframework.core.convert.ConversionService;
 import org.springframework.core.io.Resource;
 
 import com.google.common.base.Function;
@@ -82,7 +84,13 @@ public class ReloadableSpringService<T> extends AbstractReloadableService<T> imp
 
     /** List of bean post processors for this service's content. */
     @Nonnull @NonnullElements private List<BeanPostProcessor> postProcessors;
+    
+    /** Bean profiles to enable. */
+    @Nonnull @NonnullElements private Collection<String> beanProfiles;
 
+    /** Conversion service to use. */
+    @Nullable private ConversionService conversionService;
+    
     /** The class we are looking for. */
     @Nonnull private final Class<T> theClaz;
 
@@ -129,6 +137,7 @@ public class ReloadableSpringService<T> extends AbstractReloadableService<T> imp
         serviceStrategy = Constraint.isNotNull(strategy, "Strategy cannot be null");
         factoryPostProcessors = Collections.emptyList();
         postProcessors = Collections.emptyList();
+        beanProfiles = Collections.emptyList();
     }
 
     /**
@@ -163,11 +172,11 @@ public class ReloadableSpringService<T> extends AbstractReloadableService<T> imp
     }
 
     /**
-     * Sets the list of configurations for this service.
+     * Set the list of configurations for this service.
      * 
      * This setting can not be changed after the service has been initialized.
      * 
-     * @param configs list of configurations for this service, may be null or empty
+     * @param configs list of configurations for this service
      */
     public void setServiceConfigurations(@Nonnull @NonnullElements final List<Resource> configs) {
         ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
@@ -233,6 +242,34 @@ public class ReloadableSpringService<T> extends AbstractReloadableService<T> imp
 
         postProcessors = new ArrayList<>(Collections2.filter(processors, Predicates.notNull()));
     }
+    
+    /**
+     * Set the bean profiles for this service.
+     * 
+     * @param profiles bean profiles to apply
+     * 
+     * @since 5.4.0
+     */
+    public void setBeanProfiles(@Nonnull @NonnullElements final Collection<String> profiles) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
+        
+        beanProfiles = StringSupport.normalizeStringCollection(profiles);
+    }
+    
+    /**
+     * Set a conversion service to use.
+     * 
+     * @param service conversion service
+     * 
+     * @since 5.4.0
+     */
+    public void setConversionService(@Nullable final ConversionService service) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
+
+        conversionService = service;
+    }
 
     /** {@inheritDoc} */
     @Override
@@ -257,8 +294,8 @@ public class ReloadableSpringService<T> extends AbstractReloadableService<T> imp
     }
 
 
+// Checkstyle: CyclomaticComplexity OFF
     /** {@inheritDoc} */
-    // Checkstyle: CyclomaticComplexity OFF
     @Override protected boolean shouldReload() {
         // Loop over each resource and check if the any resources have been changed since
         // the last time the service was reloaded. I believe a read lock is all we need here
@@ -318,8 +355,7 @@ public class ReloadableSpringService<T> extends AbstractReloadableService<T> imp
 
         return configResourceChanged;
     }
-
-    // Checkstyle: CyclomaticComplexity ON
+// Checkstyle: CyclomaticComplexity ON
 
     /** {@inheritDoc} */
     @Override protected void doReload() {
@@ -329,9 +365,14 @@ public class ReloadableSpringService<T> extends AbstractReloadableService<T> imp
         log.debug("{} Reloading from {}", getLogPrefix(), getServiceConfigurations());
         final GenericApplicationContext appContext;
         try {
-            appContext =
-                    SpringSupport.newContext(getId(), getServiceConfigurations(), factoryPostProcessors, postProcessors,
-                            Collections.<ApplicationContextInitializer> emptyList(), getParentContext());
+            appContext = new ApplicationContextBuilder()
+                .setName(getId()).setParentContext(getParentContext())
+                .setServiceConfigurations(getServiceConfigurations())
+                .setBeanFactoryPostProcessors(factoryPostProcessors)
+                .setBeanPostProcessors(postProcessors)
+                .setBeanProfiles(beanProfiles)
+                .setConversionService(conversionService)
+                .build();
         } catch (final FatalBeanException e) {
             throw new ServiceException(e);
         }
diff --git a/src/main/java/net/shibboleth/ext/spring/util/ApplicationContextBuilder.java b/src/main/java/net/shibboleth/ext/spring/util/ApplicationContextBuilder.java
new file mode 100644
index 0000000..075e877
--- /dev/null
+++ b/src/main/java/net/shibboleth/ext/spring/util/ApplicationContextBuilder.java
@@ -0,0 +1,261 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID 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 net.shibboleth.ext.spring.util;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import net.shibboleth.ext.spring.config.BooleanToPredicateConverter;
+import net.shibboleth.ext.spring.config.DurationToLongConverter;
+import net.shibboleth.ext.spring.config.StringBooleanToPredicateConverter;
+import net.shibboleth.ext.spring.config.StringToIPRangeConverter;
+import net.shibboleth.ext.spring.config.StringToResourceConverter;
+import net.shibboleth.ext.spring.context.FilesystemGenericApplicationContext;
+import net.shibboleth.ext.spring.resource.PreferFileSystemResourceLoader;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
+import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
+import net.shibboleth.utilities.java.support.primitive.StringSupport;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
+import org.springframework.beans.factory.config.BeanPostProcessor;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextInitializer;
+import org.springframework.context.support.ConversionServiceFactoryBean;
+import org.springframework.context.support.GenericApplicationContext;
+import org.springframework.core.convert.ConversionService;
+import org.springframework.core.io.Resource;
+
+import com.google.common.base.Predicates;
+import com.google.common.collect.Collections2;
+
+/**
+ * Fluent builder for a {@link FilesystemGenericApplicationContext} equipped with various standard features,
+ * behavior, converters, etc.
+ * 
+ * @since 5.4.0
+ */
+public class ApplicationContextBuilder {
+
+    /** Class logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(ApplicationContextBuilder.class);
+
+    /** Context name. */
+    @Nullable @NotEmpty private String contextName;
+    
+    /** List of configuration resources for this service. */
+    @Nullable @NonnullElements private List<Resource> configurationResources;
+
+    /** Conversion service to use. */
+    @Nullable private ConversionService conversionService;
+    
+    /** List of context initializers. */
+    @Nullable @NonnullElements private List<ApplicationContextInitializer<? super FilesystemGenericApplicationContext>>
+    contextInitializers;
+
+    /** List of bean factory post processors for this service's content. */
+    @Nullable @NonnullElements private List<BeanFactoryPostProcessor> factoryPostProcessors;
+
+    /** List of bean post processors for this service's content. */
+    @Nullable @NonnullElements private List<BeanPostProcessor> postProcessors;
+    
+    /** Bean profiles to enable. */
+    @Nullable @NonnullElements private Collection<String> beanProfiles;
+
+    /** Application context owning this engine. */
+    @Nullable private ApplicationContext parentContext;
+    
+    /**
+     * Set the name of the context.
+     * 
+     * @param name name
+     * 
+     * @return this builder
+     */
+    @Nonnull public ApplicationContextBuilder setName(@Nullable @NotEmpty final String name) {
+        contextName = StringSupport.trimOrNull(name);
+        
+        return this;
+    }
+
+    /**
+     * Set a conversion service to use.
+     * 
+     * @param service conversion service
+     * 
+     * @return this builder
+     */
+    @Nonnull public ApplicationContextBuilder setConversionService(@Nullable final ConversionService service) {
+        conversionService = service;
+        
+        return this;
+    }
+    
+    /**
+     * Set the list of configurations for this context.
+     * 
+     * @param configs list of configurations for this context
+     * 
+     * @return this builder
+     */
+    @Nonnull public ApplicationContextBuilder setServiceConfigurations(
+            @Nonnull @NonnullElements final List<Resource> configs) {
+        configurationResources = new ArrayList<>(Collections2.filter(configs, Predicates.notNull()));
+        
+        return this;
+    }
+
+    /**
+     * Set the list of context initializers for this context.
+     * 
+     * @param initializers initializers to apply
+     * 
+     * @return this builder
+     */
+    @Nonnull public ApplicationContextBuilder setContextInitializers(
+            @Nonnull @NonnullElements
+            final List<ApplicationContextInitializer<? super FilesystemGenericApplicationContext>> initializers) {
+        contextInitializers = new ArrayList<>(Collections2.filter(initializers, Predicates.notNull()));
+        
+        return this;
+    }
+    
+    /**
+     * Set the list of bean factory post processors for this context.
+     * 
+     * @param processors bean factory post processors to apply
+     * 
+     * @return this builder
+     */
+    @Nonnull public ApplicationContextBuilder setBeanFactoryPostProcessors(
+            @Nonnull @NonnullElements final List<BeanFactoryPostProcessor> processors) {
+        factoryPostProcessors = new ArrayList<>(Collections2.filter(processors, Predicates.notNull()));
+        
+        return this;
+    }
+
+    /**
+     * Set the list of bean post processors for this context.
+     * 
+     * @param processors bean post processors to apply
+     * 
+     * @return this builder
+     */
+    @Nonnull public ApplicationContextBuilder setBeanPostProcessors(
+            @Nonnull @NonnullElements final List<BeanPostProcessor> processors) {
+        postProcessors = new ArrayList<>(Collections2.filter(processors, Predicates.notNull()));
+        
+        return this;
+    }
+    
+    /**
+     * Set the bean profiles for this context.
+     * 
+     * @param profiles bean profiles to apply
+     * 
+     * @return this builder
+     */
+    @Nonnull public ApplicationContextBuilder setBeanProfiles(
+            @Nonnull @NonnullElements final Collection<String> profiles) {
+        beanProfiles = StringSupport.normalizeStringCollection(profiles);
+        
+        return this;
+    }
+    
+    /**
+     * Set a custom {@link ConversionService} to use.
+     * 
+     * @param context parent context
+     * 
+     * @return this builder
+     */
+    @Nonnull public ApplicationContextBuilder setParentContext(@Nullable final ApplicationContext context) {
+        parentContext = context;
+        
+        return this;
+    }
+    
+// Checkstyle: CyclomaticComplexity OFF
+    /**
+     * Build the context.
+     * 
+     * @return the built context, initialized and loaded
+     */
+    @Nonnull public GenericApplicationContext build() {
+        
+        final GenericApplicationContext context = new FilesystemGenericApplicationContext(parentContext);
+        context.setDisplayName("ApplicationContext:" + contextName != null ? contextName : "anonymous");
+        
+        context.setResourceLoader(new PreferFileSystemResourceLoader());
+        
+        if (conversionService != null) {
+            context.getBeanFactory().setConversionService(conversionService);
+        } else {
+            final ConversionServiceFactoryBean service = new ConversionServiceFactoryBean();
+            service.setConverters(new HashSet<>(Arrays.asList(
+                    new DurationToLongConverter(),
+                    new StringToIPRangeConverter(),
+                    new BooleanToPredicateConverter(),
+                    new StringBooleanToPredicateConverter(),
+                    new StringToResourceConverter())));
+            service.afterPropertiesSet();
+            context.getBeanFactory().setConversionService(service.getObject());
+        }
+        
+        if (factoryPostProcessors != null) {
+            for (final BeanFactoryPostProcessor bfpp : factoryPostProcessors) {
+                context.addBeanFactoryPostProcessor(bfpp);
+            }
+        }
+
+        if (postProcessors != null) {
+            for (final BeanPostProcessor bpp : postProcessors) {
+                context.getBeanFactory().addBeanPostProcessor(bpp);
+            }
+        }
+        
+        if (beanProfiles != null) {
+            context.getEnvironment().setActiveProfiles(beanProfiles.toArray(new String[0]));
+        }
+
+        final SchemaTypeAwareXMLBeanDefinitionReader beanDefinitionReader =
+                new SchemaTypeAwareXMLBeanDefinitionReader(context);
+
+        if (configurationResources != null) {
+            beanDefinitionReader.loadBeanDefinitions(configurationResources.toArray(new Resource[] {}));
+        }
+
+        if (contextInitializers != null) {
+            for (final ApplicationContextInitializer initializer : contextInitializers) {
+                initializer.initialize(context);
+            }
+        }
+
+        context.refresh();
+        return context;
+    }
+// Checkstyle: CyclomaticComplexity ON
+    
+}
\ No newline at end of file
diff --git a/src/main/java/net/shibboleth/ext/spring/util/SpringSupport.java b/src/main/java/net/shibboleth/ext/spring/util/SpringSupport.java
index 553d34c..42c19b1 100644
--- a/src/main/java/net/shibboleth/ext/spring/util/SpringSupport.java
+++ b/src/main/java/net/shibboleth/ext/spring/util/SpringSupport.java
@@ -95,7 +95,9 @@ public final class SpringSupport {
      * @return the created context
      * 
      * TODO: The signature here needs to constrain the ApplicationContextInitializers supplied to
-     * be safe for use with a FilesystemGenericApplicationContext. The raw types are masking the bug. 
+     * be safe for use with a FilesystemGenericApplicationContext. The raw types are masking the bug.
+     * 
+     *  @deprecated
      */
     @Nonnull public static GenericApplicationContext newContext(@Nonnull @NotEmpty final String name,
             @Nonnull @NonnullElements final List<Resource> configurationResources,
diff --git a/src/test/java/net/shibboleth/ext/spring/service/NonReloadableTestBean.java b/src/test/java/net/shibboleth/ext/spring/service/NonReloadableTestBean.java
new file mode 100644
index 0000000..8eb4b5a
--- /dev/null
+++ b/src/test/java/net/shibboleth/ext/spring/service/NonReloadableTestBean.java
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID 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 net.shibboleth.ext.spring.service;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
+import net.shibboleth.utilities.java.support.component.AbstractInitializableComponent;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+
+/**
+ * Test bean to canary out behavior of reloadable bean service.
+ */
+public class NonReloadableTestBean extends AbstractInitializableComponent {
+
+    @Nonnull private final Logger log = LoggerFactory.getLogger(NonReloadableTestBean.class);
+    
+    @Nonnull @NotEmpty private final String id;
+    
+    @Nullable private ReloadableTestBean child;
+    
+    public NonReloadableTestBean(@Nonnull @NotEmpty final String name) {
+        id = name;
+        log.debug("NonReloadableTestBean {} created", id);
+    }
+
+    /** {@inheritDoc} */
+    protected void doInitialize() throws ComponentInitializationException {
+        super.doInitialize();
+        
+        log.debug("NonReloadableTestBean {} initialized", id);
+    }
+
+    /** {@inheritDoc} */
+    protected void doDestroy() {
+        log.debug("NonReloadableTestBean {} destroyed", id);
+        
+        super.doDestroy();
+    }
+    
+    public ReloadableTestBean getChild() {
+        log.debug("NonReloadableTestBean {} child getter called", id);
+        
+        return child;
+    }
+    
+    public void setChild(ReloadableTestBean b) {
+        child = b;
+    }
+    
+    public int getValue() {
+        return getChild().getValue();
+    }
+    
+}
\ No newline at end of file
diff --git a/src/test/java/net/shibboleth/ext/spring/service/ReloadableBeanServiceTest.java b/src/test/java/net/shibboleth/ext/spring/service/ReloadableBeanServiceTest.java
new file mode 100644
index 0000000..3d637c3
--- /dev/null
+++ b/src/test/java/net/shibboleth/ext/spring/service/ReloadableBeanServiceTest.java
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID 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 net.shibboleth.ext.spring.service;
+
+import java.io.IOException;
+import java.util.Collections;
+
+import net.shibboleth.ext.spring.util.ApplicationContextBuilder;
+import net.shibboleth.utilities.java.support.service.ReloadableService;
+import net.shibboleth.utilities.java.support.service.ServiceableComponent;
+
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.support.GenericApplicationContext;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.core.io.Resource;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+public class ReloadableBeanServiceTest {
+
+    @Test public void reloadableService() throws IOException, InterruptedException {
+        
+        final GenericApplicationContext appCtx = new ApplicationContextBuilder()
+                .setName("appCtx")
+                .setServiceConfigurations(Collections.<Resource>singletonList(
+                        new ClassPathResource("net/shibboleth/ext/spring/service/ReloadableBeans1.xml")))
+                .build();
+
+        try {
+            final NonReloadableTestBean bean = appCtx.getBean("nonReloadableBean", NonReloadableTestBean.class);
+            Assert.assertEquals(10, bean.getValue());
+            
+            final ReloadableTestBean child1 = bean.getChild();
+            
+            final ReloadableService embedded = (ReloadableService) appCtx.getBean("reloadableBeanService");
+            
+            final ServiceableComponent<ApplicationContext> component = embedded.getServiceableComponent();
+            try {
+                Assert.assertFalse(component.getComponent().containsLocalBean("reloadableBeanService"));
+            } finally {
+                component.unpinComponent();
+            }
+            
+            embedded.reload();
+
+            final ReloadableTestBean child2 = bean.getChild();
+
+            Assert.assertNotSame(child1, child2);
+            
+        } finally {
+            appCtx.close();
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/src/test/java/net/shibboleth/ext/spring/service/ReloadableSpringServiceTest.java b/src/test/java/net/shibboleth/ext/spring/service/ReloadableSpringServiceTest.java
index f9f283b..52373eb 100644
--- a/src/test/java/net/shibboleth/ext/spring/service/ReloadableSpringServiceTest.java
+++ b/src/test/java/net/shibboleth/ext/spring/service/ReloadableSpringServiceTest.java
@@ -23,14 +23,11 @@ import java.io.IOException;
 import java.io.OutputStream;
 import java.util.Collections;
 
-import net.shibboleth.ext.spring.util.SpringSupport;
+import net.shibboleth.ext.spring.util.ApplicationContextBuilder;
 import net.shibboleth.utilities.java.support.service.ServiceableComponent;
 
 import org.joda.time.DateTime;
 import org.springframework.beans.factory.BeanInitializationException;
-import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
-import org.springframework.beans.factory.config.BeanPostProcessor;
-import org.springframework.context.ApplicationContextInitializer;
 import org.springframework.context.support.GenericApplicationContext;
 import org.springframework.core.io.ClassPathResource;
 import org.springframework.core.io.FileSystemResource;
@@ -240,7 +237,7 @@ public class ReloadableSpringServiceTest {
             Thread.sleep(RELOAD_DELAY);
             count--;
         }
-        Assert.assertTrue(component.isDestroyed(), "After 7 second component has still not be destroyed");
+        Assert.assertTrue(component.isDestroyed(), "After 7 seconds component has still not be destroyed");
 
         testFile.delete();
     }
@@ -249,10 +246,10 @@ public class ReloadableSpringServiceTest {
 
         final Resource parentResource = new ClassPathResource("net/shibboleth/ext/spring/service/ReloadableSpringService.xml");
 
-        final GenericApplicationContext appCtx =
-                SpringSupport.newContext("appCtx", Collections.singletonList(parentResource),
-                        Collections.<BeanFactoryPostProcessor>emptyList(), Collections.<BeanPostProcessor>emptyList(),
-                        Collections.<ApplicationContextInitializer>emptyList(), null);
+        final GenericApplicationContext appCtx = new ApplicationContextBuilder()
+                .setName("appCtx")
+                .setServiceConfigurations(Collections.singletonList(parentResource))
+                .build();
         try {
             final ReloadableSpringService service = appCtx.getBean("testReloadableSpringService", ReloadableSpringService.class);
     
@@ -266,10 +263,10 @@ public class ReloadableSpringServiceTest {
 
         final Resource parentResource = new ClassPathResource("net/shibboleth/ext/spring/service/ReloadableSpringService.xml");
 
-        final GenericApplicationContext appCtx =
-                SpringSupport.newContext("appCtx", Collections.singletonList(parentResource),
-                        Collections.<BeanFactoryPostProcessor>emptyList(), Collections.<BeanPostProcessor>emptyList(),
-                        Collections.<ApplicationContextInitializer>emptyList(), null);
+        final GenericApplicationContext appCtx = new ApplicationContextBuilder()
+                .setName("appCtx")
+                .setServiceConfigurations(Collections.singletonList(parentResource))
+                .build();
         try {
             final ReloadableSpringService service1 =
                     appCtx.getBean("testReloadableSpringService", ReloadableSpringService.class);
diff --git a/src/test/java/net/shibboleth/ext/spring/service/ReloadableTestBean.java b/src/test/java/net/shibboleth/ext/spring/service/ReloadableTestBean.java
new file mode 100644
index 0000000..7ffc823
--- /dev/null
+++ b/src/test/java/net/shibboleth/ext/spring/service/ReloadableTestBean.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID 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 net.shibboleth.ext.spring.service;
+
+import javax.annotation.Nonnull;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
+import net.shibboleth.utilities.java.support.component.AbstractInitializableComponent;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+
+/**
+ * Test bean to canary out behavior of reloadable bean service.
+ */
+public class ReloadableTestBean extends AbstractInitializableComponent {
+
+    @Nonnull private final Logger log = LoggerFactory.getLogger(ReloadableTestBean.class);
+    
+    @Nonnull @NotEmpty private final String id;
+    
+    private int value;
+    
+    public ReloadableTestBean(@Nonnull @NotEmpty final String name, final int val) {
+        id = name;
+        value = val;
+        log.debug("ReloadableTestBean {} created", id);
+    }
+
+    /** {@inheritDoc} */
+    protected void doInitialize() throws ComponentInitializationException {
+        super.doInitialize();
+        
+        log.debug("ReloadableTestBean {} initialized", id);
+    }
+
+    /** {@inheritDoc} */
+    protected void doDestroy() {
+        log.debug("ReloadableTestBean {} destroyed", id);
+        
+        super.doDestroy();
+    }
+    
+    public int getValue() {
+        return value;
+    }
+    
+}
\ No newline at end of file
diff --git a/src/test/resources/net/shibboleth/ext/spring/service/ReloadableBeans1.xml b/src/test/resources/net/shibboleth/ext/spring/service/ReloadableBeans1.xml
new file mode 100644
index 0000000..caa597a
--- /dev/null
+++ b/src/test/resources/net/shibboleth/ext/spring/service/ReloadableBeans1.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xmlns:p="http://www.springframework.org/schema/p"
+	xmlns:c="http://www.springframework.org/schema/c"
+	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"
+    default-init-method="initialize"
+    default-destroy-method="destroy">
+        
+    <!-- This bean exists in the "top" level context. -->
+    <bean id="nonReloadableBean" class="net.shibboleth.ext.spring.service.NonReloadableTestBean"
+        c:_0="One">
+        <!-- This ensures that this singleton gets a "fresh" copy of the injected bean each call. -->
+        <lookup-method name="getChild" bean="reloadableBean" />
+    </bean>
+
+    <!-- This bean only exists in the embedded "reloadable" context because of its custom scope. -->
+    <bean id="reloadableBean" class="net.shibboleth.ext.spring.service.ReloadableTestBean" scope="reloadable"
+        c:_0="Two" c:_1="10" />
+
+    <!--
+    This machinery installs an embedded copy of this same bean set into itself. The "default" profile ensures that
+    the embedded copy ignores these beans because the bean profile there is overridden to a non-default value.
+    The BeanFactoryPostProcessor ensures that the embedded version's reloadable beans become singletons and the
+    rest are ignored (via lazy-init). Most of this can be buried in parent beans.
+    -->
+    <beans profile="default">
+        <bean id="reloadableBeanService" class="net.shibboleth.ext.spring.service.ReloadableSpringService"
+                p:beanProfiles="reloadable"
+                p:serviceConfigurations="classpath:/net/shibboleth/ext/spring/service/ReloadableBeans1.xml">
+            <constructor-arg name="claz" value="org.springframework.context.ApplicationContext" />
+            <constructor-arg name="strategy">
+                <bean class="net.shibboleth.ext.spring.service.ApplicationContextServiceStrategy" />
+            </constructor-arg>
+            <property name="beanFactoryPostProcessors">
+                <bean class="net.shibboleth.ext.spring.config.NonReloadableExcluder" />
+            </property>
+        </bean>
+
+	    <bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
+	        <property name="scopes">
+	            <map>
+	                <entry key="reloadable">
+	                    <bean class="net.shibboleth.ext.spring.config.ReloadableScope"
+                            c:service-ref="reloadableBeanService" />
+	                </entry>
+	            </map>
+	        </property>
+	    </bean>
+    </beans>
+</beans>

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.


More information about the commits mailing list