[java-shib-shared] branch main updated: JSSH-71 Remove the impact of the DestructableComponent Interface

Codeberg noreply at shibboleth.net
Thu Jul 23 08:57:59 UTC 2026


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

codeberg pushed a commit to branch main
in repository java-shib-shared.

View the commit online:
https://codeberg.org/Shibboleth/java-shib-shared/commit/5037f03d14d7f5afe0fef8555d4a4b4afb77a417

The following commit(s) were added to refs/heads/main by this push:
     new 5037f03d JSSH-71 Remove the impact of the DestructableComponent Interface
5037f03d is described below

commit 5037f03d14d7f5afe0fef8555d4a4b4afb77a417
Author: Rod Widdowson <rdw at steadingsoftware.com>
AuthorDate: Wed Jul 22 20:03:39 2026 +0100

    JSSH-71 Remove the impact of the DestructableComponent Interface
    
    https://shibboleth.atlassian.net/browse/JSSH-71
    
    Add a bean which will tear down all its ApplicationContext peers
    at our annotation point.
---
 .../spring/config/BeanTearDownProcessor.java       | 74 +++++++++++++++++
 .../spring/config/BeanTearDownProcessorTest.java   | 93 ++++++++++++++++++++++
 .../config/beanTearDownPostProcessorTest.xml       | 19 +++++
 .../shared/primitive/AnnotationsSupport.java       | 40 +++++++---
 4 files changed, 213 insertions(+), 13 deletions(-)

diff --git a/shib-spring/src/main/java/net/shibboleth/shared/spring/config/BeanTearDownProcessor.java b/shib-spring/src/main/java/net/shibboleth/shared/spring/config/BeanTearDownProcessor.java
new file mode 100644
index 00000000..b8028819
--- /dev/null
+++ b/shib-spring/src/main/java/net/shibboleth/shared/spring/config/BeanTearDownProcessor.java
@@ -0,0 +1,74 @@
+/*
+ * 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.config;
+
+import javax.annotation.Nullable;
+
+import org.slf4j.Logger;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.DisposableBean;
+import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.context.ConfigurableApplicationContext;
+
+import net.shibboleth.shared.annotation.OnTeardown;
+import net.shibboleth.shared.primitive.AnnotationsSupport;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * A bean to teardown all beans in the application Context that have
+ * the {@link OnTeardown} annotation.
+ *
+ * @since 9.3.0
+ */
+public class BeanTearDownProcessor implements ApplicationContextAware, DisposableBean {
+
+    /** Logger. */
+    final private Logger log = LoggerFactory.getLogger(BeanTearDownProcessor.class);
+
+    /**
+     * If non null, the {@link ApplicationContext} that we were created inside.
+     */
+    @Nullable private ApplicationContext applicationContext = null;
+
+    /** {@inheritDoc}
+     * <p>If there is an {@link ApplicationContext} then iterate over all beans and call all
+     * the annotated tear down methods that each one implements (if any).</p>
+     */
+    @Override
+    public void destroy() throws Exception {
+
+        if (applicationContext == null) {
+            return;
+        }
+
+        if (applicationContext instanceof ConfigurableApplicationContext) {
+            final ConfigurableListableBeanFactory clbf = ((ConfigurableApplicationContext) applicationContext).getBeanFactory();
+            for (final String beanName : applicationContext.getBeanDefinitionNames()) {
+                final Object bean = clbf.getSingleton(beanName);
+                AnnotationsSupport.callOnTeardownAnnotations(bean, beanName, log);
+            }
+        }
+    }
+
+    /** {@inheritDoc}
+     * <p>We save the {@link ApplicationContext} for use in teardown.</p>
+     */
+    @Override
+    public void setApplicationContext( @Nullable final ApplicationContext ctx) throws BeansException {
+        applicationContext = ctx;
+    }
+}
\ No newline at end of file
diff --git a/shib-spring/src/test/java/net/shibboleth/shared/spring/config/BeanTearDownProcessorTest.java b/shib-spring/src/test/java/net/shibboleth/shared/spring/config/BeanTearDownProcessorTest.java
new file mode 100644
index 00000000..7723a18c
--- /dev/null
+++ b/shib-spring/src/test/java/net/shibboleth/shared/spring/config/BeanTearDownProcessorTest.java
@@ -0,0 +1,93 @@
+/*
+ * 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.config;
+
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+
+import org.springframework.beans.factory.DisposableBean;
+import org.springframework.context.support.GenericApplicationContext;
+import org.springframework.core.io.ClassPathResource;
+import org.testng.annotations.Test;
+
+import net.shibboleth.shared.annotation.OnTeardown;
+import net.shibboleth.shared.spring.util.ApplicationContextBuilder;
+
+/**
+ * Test for {@link BeanTearDownProcessor}.
+ *
+ * Summon up some beans, destroy the ApplicationContext.  look at the side effects.
+ */
+ at SuppressWarnings("javadoc")
+public class BeanTearDownProcessorTest  {
+
+    @Test
+    public void test() {
+        //
+        // We need to own the Context lifetime
+        //
+        final ApplicationContextBuilder builder = new ApplicationContextBuilder();
+        final ClassPathResource res = new ClassPathResource("net/shibboleth/shared/spring/config/beanTearDownPostProcessorTest.xml");
+        builder.setServiceConfiguration(res);
+        final GenericApplicationContext applicationContext = builder.build();
+        final WithAnnotationChild childWith = (WithAnnotationChild) applicationContext.getBean("ChildWith");
+        final NoAnnotationParent noneParent  = (NoAnnotationParent) applicationContext.getBean("ParentWithout");
+        final NoAnnotationParent noneChild =(NoAnnotationParent) applicationContext.getBean("ChildWithout");
+
+        assertFalse(childWith.tornDownChild);
+        assertFalse(childWith.tornDownParent);
+        assertFalse(noneParent.tornDown);
+        assertFalse(noneChild.tornDown);
+
+        applicationContext.close();
+        assertTrue(childWith.tornDownChild); //annotated
+        assertTrue(childWith.tornDownParent); // annotated, different method
+        assertFalse(noneParent.tornDown); // Not a Disposable Bean
+        assertTrue(noneChild.tornDown);
+    }
+
+    /** Test class to check both tear downs are called */
+    public static class WithAnnotationParent {
+
+        public boolean tornDownParent = false;
+
+        @OnTeardown public final void tearDownWithAnnotationParent() {
+            tornDownParent = true;
+        }
+    }
+
+    /** Test class to check both tear downs are called */
+    public static class WithAnnotationChild extends WithAnnotationParent{
+
+        public boolean tornDownChild= false;
+
+        @OnTeardown public final void tearDownWithAnnotationChild() {
+            tornDownChild = true;
+        }
+    }
+
+    public static class NoAnnotationParent {
+
+        public boolean tornDown = false;
+
+        public final void destroy() {
+            tornDown = true;
+        }
+    }
+
+    public static class NoAnnotationChild extends NoAnnotationParent implements DisposableBean {
+
+    }
+}
diff --git a/shib-spring/src/test/resources/net/shibboleth/shared/spring/config/beanTearDownPostProcessorTest.xml b/shib-spring/src/test/resources/net/shibboleth/shared/spring/config/beanTearDownPostProcessorTest.xml
new file mode 100644
index 00000000..73681908
--- /dev/null
+++ b/shib-spring/src/test/resources/net/shibboleth/shared/spring/config/beanTearDownPostProcessorTest.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+	xmlns:p="http://www.springframework.org/schema/p" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"
+	default-init-method="initialize" >
+
+	<bean class="net.shibboleth.shared.spring.config.BeanTearDownProcessor" />
+
+	<bean id="ChildWith"
+		class="net.shibboleth.shared.spring.config.BeanTearDownProcessorTest$WithAnnotationChild" />
+
+	<bean id="ParentWithout"
+        class="net.shibboleth.shared.spring.config.BeanTearDownProcessorTest$NoAnnotationParent" />
+
+	<bean id="ChildWithout"
+        class="net.shibboleth.shared.spring.config.BeanTearDownProcessorTest$NoAnnotationChild" />
+
+
+</beans>
\ No newline at end of file
diff --git a/shib-support/src/main/java/net/shibboleth/shared/primitive/AnnotationsSupport.java b/shib-support/src/main/java/net/shibboleth/shared/primitive/AnnotationsSupport.java
index a09599e8..605629f4 100644
--- a/shib-support/src/main/java/net/shibboleth/shared/primitive/AnnotationsSupport.java
+++ b/shib-support/src/main/java/net/shibboleth/shared/primitive/AnnotationsSupport.java
@@ -16,6 +16,7 @@ package net.shibboleth.shared.primitive;
 
 import java.lang.reflect.Method;
 
+import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 
 import org.slf4j.Logger;
@@ -39,35 +40,48 @@ public final class AnnotationsSupport {
     static public void callOnTeardownAnnotations(@Nullable final Object obj, @Nullable final String id, @Nullable final Logger log) {
 
         Logger theLog = log;
-        String theName = id;
         if (obj == null) {
             return;
         }
         for (final Method m: obj.getClass().getMethods()) {
             if (m.getParameterCount() == 0 && m.getAnnotation(OnTeardown.class) != null) {
-                if (theName == null) {
-                    if (obj instanceof IdentifiedComponent) {
-                        theName = ((IdentifiedComponent)obj).getId();
-                    }
-                    if (theName == null) {
-                        theName = "Unidentified bean of type " + obj.getClass().getName();
-                    }
-                }
-                if (log != null) {
-                    log.debug("{}: found an OnTeardown method {}", id, m);
+                if (log != null && log.isDebugEnabled()) {
+                    log.debug("{}: found an OnTeardown method {}", getNameFor(obj, id));
                 }
                 try {
                     m.invoke(obj);
-                } catch (final Exception e) {
+                }
+                catch (final Exception e) {
                     if (theLog == null) {
                         theLog = LoggerFactory.getLogger(AnnotationsSupport.class);
                     }
-                    theLog.error("{}: Failed invoking {}", id, m, e);
+                    theLog.error("{}: Failed invoking {}", getNameFor(obj, id), m, e);
                 }
             }
         }
     }
     
+    /**
+     * Helper method to find a log-friendly name for an object.
+     * <p>Return the supplied name if non null, otherwise interrogate for
+     * its identity (if it has one), otherwise generate something.</p>
+     * @param obj The object we are interested it
+     * @param suppliedName the name, if we know it already
+     * @return the best name we could
+     */
+    static @Nonnull String getNameFor(@Nonnull final Object obj, @Nullable String suppliedName) {
+        String result = suppliedName;
+        if (suppliedName == null) {
+            if (obj instanceof IdentifiedComponent) {
+                suppliedName = ((IdentifiedComponent)obj).getId();
+            }
+            if (suppliedName == null) {
+                suppliedName = "Unidentified bean of type " + obj.getClass().getName();
+            }
+        }
+        return result;
+    }
+
     /**
      * Call the {@link OnTeardown} methods.
      * @param obj The object to introspect

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


More information about the commits mailing list