[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