[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