[java-shib-shared] 02/02: JSSH-41 - Add nullable factory bean support
Scott Cantor
cantor.2 at osu.edu
Wed Oct 25 14:59:25 UTC 2023
This is an automated email from the git hooks/post-receive script.
scantor pushed a commit to branch main
in repository java-shib-shared.
View the commit online:
http://git.shibboleth.net/view/?p=java-shib-shared.git;a=commit;h=2dbc50df31d22b008d8824eddad09f13a79dc77b
commit 2dbc50df31d22b008d8824eddad09f13a79dc77b
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Wed Oct 25 10:59:19 2023 -0400
JSSH-41 - Add nullable factory bean support
https://shibboleth.atlassian.net/browse/JSSH-41
---
.../factory/AbstractComponentAwareFactoryBean.java | 53 +++-
.../shared/spring/factory/AbstractFactoryBean.java | 301 +++++++++++++++++++++
2 files changed, 344 insertions(+), 10 deletions(-)
diff --git a/shib-spring/src/main/java/net/shibboleth/shared/spring/factory/AbstractComponentAwareFactoryBean.java b/shib-spring/src/main/java/net/shibboleth/shared/spring/factory/AbstractComponentAwareFactoryBean.java
index 47c6ffe4..ebb9f3fc 100644
--- a/shib-spring/src/main/java/net/shibboleth/shared/spring/factory/AbstractComponentAwareFactoryBean.java
+++ b/shib-spring/src/main/java/net/shibboleth/shared/spring/factory/AbstractComponentAwareFactoryBean.java
@@ -14,11 +14,9 @@
package net.shibboleth.shared.spring.factory;
-import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.springframework.beans.factory.BeanCreationException;
-import org.springframework.beans.factory.config.AbstractFactoryBean;
import net.shibboleth.shared.component.DestructableComponent;
import net.shibboleth.shared.component.InitializableComponent;
@@ -30,29 +28,64 @@ import net.shibboleth.shared.primitive.LoggerFactory;
* @param <T> The type to implement
*/
public abstract class AbstractComponentAwareFactoryBean<T> extends AbstractFactoryBean<T> {
+
+ /** Flag controlling null behavior. */
+ private boolean throwIfNull;
+
+ /**
+ * Constructor.
+ */
+ public AbstractComponentAwareFactoryBean() {
+ throwIfNull = true;
+ }
- /** {@inheritDoc}. Call our destroy method if aposite. */
+ /**
+ * {@inheritDoc}
+ *
+ * <p>Call our destroy method if aposite.</p>
+ */
@Override protected void destroyInstance(@Nullable final T instance) throws Exception {
super.destroyInstance(instance);
- if (instance instanceof DestructableComponent) {
- ((DestructableComponent) instance).destroy();
+ if (instance instanceof DestructableComponent dc) {
+ dc.destroy();
}
}
+
+ /**
+ * Sets whether to raise an exception if a null is returned from {@link #doCreateInstance}.
+ *
+ * <p>Defaults to true.</p>
+ *
+ * @param flag
+ *
+ * @since 9.1.0
+ */
+ public void setThrowIfNull(final boolean flag) {
+ throwIfNull = flag;
+ }
/**
- * Call the parent class to create the object, then initialize it aposite. {@inheritDoc}.
+ * <p>Call the parent class to create the object, then initialize it aposite.</p>
+ *
+ * {@inheritDoc}.
*/
@Override
- @Nonnull protected final T createInstance() throws Exception {
+ @Nullable protected final T createInstance() throws Exception {
if (!isSingleton()) {
LoggerFactory.getLogger(AbstractComponentAwareFactoryBean.class).error(
"Configuration error: {} should not be used to create prototype beans."
+ " Destroy is never called for prototype beans", AbstractComponentAwareFactoryBean.class);
throw new BeanCreationException("Do not use AbstractComponentAwareFactoryBean to create prototype beans");
}
+
final T theBean = doCreateInstance();
- if (theBean instanceof InitializableComponent) {
- ((InitializableComponent) theBean).initialize();
+
+ if (throwIfNull && theBean == null) {
+ throw new BeanCreationException("Bean was null and throwIfNull was set");
+ }
+
+ if (theBean instanceof InitializableComponent ic) {
+ ic.initialize();
}
return theBean;
}
@@ -63,6 +96,6 @@ public abstract class AbstractComponentAwareFactoryBean<T> extends AbstractFacto
* @return the bean.
* @throws Exception if needed.
*/
- @Nonnull protected abstract T doCreateInstance() throws Exception;
+ @Nullable protected abstract T doCreateInstance() throws Exception;
}
\ No newline at end of file
diff --git a/shib-spring/src/main/java/net/shibboleth/shared/spring/factory/AbstractFactoryBean.java b/shib-spring/src/main/java/net/shibboleth/shared/spring/factory/AbstractFactoryBean.java
new file mode 100644
index 00000000..939d02a5
--- /dev/null
+++ b/shib-spring/src/main/java/net/shibboleth/shared/spring/factory/AbstractFactoryBean.java
@@ -0,0 +1,301 @@
+/*
+ * Licensed 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.shared.spring.factory;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.springframework.beans.SimpleTypeConverter;
+import org.springframework.beans.TypeConverter;
+import org.springframework.beans.factory.BeanClassLoaderAware;
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.beans.factory.BeanFactoryAware;
+import org.springframework.beans.factory.DisposableBean;
+import org.springframework.beans.factory.FactoryBean;
+import org.springframework.beans.factory.FactoryBeanNotInitializedException;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.beans.factory.config.ConfigurableBeanFactory;
+import org.springframework.util.Assert;
+import org.springframework.util.ClassUtils;
+import org.springframework.util.ObjectUtils;
+import org.springframework.util.ReflectionUtils;
+
+/**
+ * Cloned from Spring's base class to fix bug they won't acknowledge regarding
+ * the ability for {@link #createInstance} to return null.
+ *
+ * <p>Simple template superclass for {@link FactoryBean} implementations that
+ * creates a singleton or a prototype object, depending on a flag.</p>
+ *
+ * <p>If the "singleton" flag is {@code true} (the default),
+ * this class will create the object that it creates exactly once
+ * on initialization and subsequently return said singleton instance
+ * on all calls to the {@link #getObject()} method.
+ *
+ * <p>Else, this class will create a new instance every time the
+ * {@link #getObject()} method is invoked. Subclasses are responsible
+ * for implementing the abstract {@link #createInstance()} template
+ * method to actually create the object(s) to expose.
+ *
+ * @author Juergen Hoeller
+ * @author Keith Donald
+ * @param <T> the bean type
+ * @see #setSingleton
+ * @see #createInstance()
+ *
+ * @since 9.1.0
+ */
+public abstract class AbstractFactoryBean<T>
+ implements FactoryBean<T>, BeanClassLoaderAware, BeanFactoryAware, InitializingBean, DisposableBean {
+
+ private boolean singleton = true;
+
+ @Nullable
+ private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader();
+
+ @Nullable
+ private BeanFactory beanFactory;
+
+ private boolean initialized = false;
+
+ @Nullable
+ private T singletonInstance;
+
+ @Nullable
+ private T earlySingletonInstance;
+
+
+ /**
+ * Set if a singleton should be created, or a new object on each request
+ * otherwise. Default is {@code true} (a singleton).
+ *
+ * @param singleton flag to set
+ */
+ public void setSingleton(final boolean singleton) {
+ this.singleton = singleton;
+ }
+
+ /** {@inheritDoc} */
+ public boolean isSingleton() {
+ return this.singleton;
+ }
+
+ /** {@inheritDoc} */
+ public void setBeanClassLoader(@Nonnull final ClassLoader classLoader) {
+ this.beanClassLoader = classLoader;
+ }
+
+ /** {@inheritDoc} */
+ public void setBeanFactory(@Nullable BeanFactory beanFactory) {
+ this.beanFactory = beanFactory;
+ }
+
+ /**
+ * Return the BeanFactory that this bean runs in.
+ *
+ * @return bean factory
+ */
+ @Nullable
+ protected BeanFactory getBeanFactory() {
+ return this.beanFactory;
+ }
+
+ /**
+ * Obtain a bean type converter from the BeanFactory that this bean
+ * runs in. This is typically a fresh instance for each call,
+ * since TypeConverters are usually <i>not</i> thread-safe.
+ * <p>Falls back to a SimpleTypeConverter when not running in a BeanFactory.</p>
+ *
+ * @return type converter
+ *
+ * @see ConfigurableBeanFactory#getTypeConverter()
+ * @see org.springframework.beans.SimpleTypeConverter
+ */
+ @Nonnull protected TypeConverter getBeanTypeConverter() {
+ BeanFactory beanFactory = getBeanFactory();
+ if (beanFactory instanceof ConfigurableBeanFactory cbf) {
+ return cbf.getTypeConverter();
+ }
+ else {
+ return new SimpleTypeConverter();
+ }
+ }
+
+ /**
+ * Eagerly create the singleton instance, if necessary.
+ *
+ * {@inheritDoc}
+ */
+ public void afterPropertiesSet() throws Exception {
+ if (isSingleton()) {
+ this.initialized = true;
+ this.singletonInstance = createInstance();
+ this.earlySingletonInstance = null;
+ }
+ }
+
+
+ /**
+ * Expose the singleton instance or create a new prototype instance.
+ *
+ * {@inheritDoc}
+ *
+ * @see #createInstance()
+ * @see #getEarlySingletonInterfaces()
+ */
+ @Nullable public final T getObject() throws Exception {
+ if (isSingleton()) {
+ return (this.initialized ? this.singletonInstance : getEarlySingletonInstance());
+ }
+ else {
+ return createInstance();
+ }
+ }
+
+ /**
+ * Determine an 'early singleton' instance, exposed in case of a
+ * circular reference. Not called in a non-circular scenario.
+ *
+ * @return early singleton
+ *
+ * @throws Exception on error
+ */
+ @SuppressWarnings("unchecked")
+ @Nullable private T getEarlySingletonInstance() throws Exception {
+ Class<?>[] ifcs = getEarlySingletonInterfaces();
+ if (ifcs == null) {
+ throw new FactoryBeanNotInitializedException(
+ getClass().getName() + " does not support circular references");
+ }
+ if (this.earlySingletonInstance == null) {
+ this.earlySingletonInstance = (T) Proxy.newProxyInstance(
+ this.beanClassLoader, ifcs, new EarlySingletonInvocationHandler());
+ }
+ return this.earlySingletonInstance;
+ }
+
+ /**
+ * Expose the singleton instance (for access through the 'early singleton' proxy).
+ * @return the singleton instance that this FactoryBean holds
+ * @throws IllegalStateException if the singleton instance is not initialized
+ */
+ @Nullable
+ private T getSingletonInstance() throws IllegalStateException {
+ Assert.state(this.initialized, "Singleton instance not initialized yet");
+ return this.singletonInstance;
+ }
+
+ /**
+ * Destroy the singleton instance, if any.
+ *
+ * {@inheritDoc}
+ *
+ * @see #destroyInstance(Object)
+ */
+ public void destroy() throws Exception {
+ if (isSingleton()) {
+ destroyInstance(this.singletonInstance);
+ }
+ }
+
+
+ /**
+ * This abstract method declaration mirrors the method in the FactoryBean
+ * interface, for a consistent offering of abstract template methods.
+ *
+ * {@inheritDoc}
+ *
+ * @see org.springframework.beans.factory.FactoryBean#getObjectType()
+ */
+ @Nullable
+ public abstract Class<?> getObjectType();
+
+ /**
+ * Template method that subclasses must override to construct
+ * the object returned by this factory.
+ * <p>Invoked on initialization of this FactoryBean in case of
+ * a singleton; else, on each {@link #getObject()} call.
+ * @return the object returned by this factory
+ * @throws Exception if an exception occurred during object creation
+ * @see #getObject()
+ */
+ @Nullable
+ protected abstract T createInstance() throws Exception;
+
+ /**
+ * Return an array of interfaces that a singleton object exposed by this
+ * FactoryBean is supposed to implement, for use with an 'early singleton
+ * proxy' that will be exposed in case of a circular reference.
+ * <p>The default implementation returns this FactoryBean's object type,
+ * provided that it is an interface, or {@code null} otherwise. The latter
+ * indicates that early singleton access is not supported by this FactoryBean.
+ * This will lead to a FactoryBeanNotInitializedException getting thrown.
+ * @return the interfaces to use for 'early singletons',
+ * or {@code null} to indicate a FactoryBeanNotInitializedException
+ * @see org.springframework.beans.factory.FactoryBeanNotInitializedException
+ */
+ @Nullable
+ protected Class<?>[] getEarlySingletonInterfaces() {
+ Class<?> type = getObjectType();
+ return (type != null && type.isInterface() ? new Class<?>[] {type} : null);
+ }
+
+ /**
+ * Callback for destroying a singleton instance. Subclasses may
+ * override this to destroy the previously created instance.
+ * <p>The default implementation is empty.
+ * @param instance the singleton instance, as returned by
+ * {@link #createInstance()}
+ * @throws Exception in case of shutdown errors
+ * @see #createInstance()
+ */
+ protected void destroyInstance(@Nullable T instance) throws Exception {
+ }
+
+
+ /**
+ * Reflective InvocationHandler for lazy access to the actual singleton object.
+ */
+ private class EarlySingletonInvocationHandler implements InvocationHandler {
+
+ /** {@inheritDoc} */
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+ if (ReflectionUtils.isEqualsMethod(method)) {
+ // Only consider equal when proxies are identical.
+ return (proxy == args[0]);
+ }
+ else if (ReflectionUtils.isHashCodeMethod(method)) {
+ // Use hashCode of reference proxy.
+ return System.identityHashCode(proxy);
+ }
+ else if (!initialized && ReflectionUtils.isToStringMethod(method)) {
+ return "Early singleton proxy for interfaces " +
+ ObjectUtils.nullSafeToString(getEarlySingletonInterfaces());
+ }
+ try {
+ return method.invoke(getSingletonInstance(), args);
+ }
+ catch (InvocationTargetException ex) {
+ throw ex.getTargetException();
+ }
+ }
+ }
+
+}
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list