From 24f495caafeb23ec52cbab8458a96501fc36dd3b Mon Sep 17 00:00:00 2001 From: "Lincoln Baxter, III" Date: Tue, 21 Mar 2023 17:23:38 -0400 Subject: [PATCH 1/3] fix(el): all registered BeanNameResolver service providers should be attempted before failure occurs --- .../rewrite/el/TypeBasedExpression.java | 56 ++++++++++++------- 1 file changed, 35 insertions(+), 21 deletions(-) diff --git a/api-el/src/main/java/org/ocpsoft/rewrite/el/TypeBasedExpression.java b/api-el/src/main/java/org/ocpsoft/rewrite/el/TypeBasedExpression.java index 9c35621b3..d091d6344 100644 --- a/api-el/src/main/java/org/ocpsoft/rewrite/el/TypeBasedExpression.java +++ b/api-el/src/main/java/org/ocpsoft/rewrite/el/TypeBasedExpression.java @@ -15,7 +15,9 @@ */ package org.ocpsoft.rewrite.el; +import java.util.ArrayList; import java.util.Iterator; +import java.util.List; import org.ocpsoft.common.services.ServiceLoader; import org.ocpsoft.logging.Logger; @@ -62,43 +64,55 @@ public String getExpression() @SuppressWarnings("unchecked") private String lookupBeanName() { - // load the available SPI implementations Iterator iterator = ServiceLoader.load(BeanNameResolver.class).iterator(); + + List deferred = new ArrayList<>(); while (iterator.hasNext()) { BeanNameResolver resolver = iterator.next(); - // check if this implementation is able to tell the name - String beanName = resolver.getBeanName(clazz); - - if (log.isTraceEnabled()) { - log.trace("Service provider [{}] returned [{}] for class [{}]", new Object[] { - resolver.getClass().getSimpleName(), beanName, clazz.getName() - }); - } - - // the first result is accepted - if (beanName != null) { - - // create the complete EL expression including the component - String el = new StringBuilder() - .append(beanName).append('.').append(component) - .toString(); + try { + // check if this implementation is able to tell the name + String beanName = resolver.getBeanName(clazz); if (log.isTraceEnabled()) { - log.debug("Creation of EL expression for component [{}] of class [{}] successful: {}", new Object[] { - component, clazz.getName(), el + log.trace("Service provider [{}] returned [{}] for class [{}]", new Object[] { + resolver.getClass().getSimpleName(), beanName, clazz.getName() }); } - return el; + // the first result is accepted + if (beanName != null) { + + // create the complete EL expression including the component + String el = new StringBuilder() + .append(beanName).append('.').append(component) + .toString(); + + if (log.isTraceEnabled()) { + log.debug("Creation of EL expression for component [{}] of class [{}] successful: {}", new Object[] { + component, clazz.getName(), el + }); + } + + return el; + } + } + catch (Exception e) { + log.debug("Failed to resolve bean names using [" + resolver.getClass().getName() + "]", e); + deferred.add(e); } } + if (deferred.size() > 1) { + for (Exception e : deferred) { + log.error("Failed to resolve bean names.", e); + } + } throw new IllegalStateException("Unable to obtain EL name for bean of type [" + clazz.getName() + "] from any of the SPI implementations. You should conside placing a @" - + ELBeanName.class.getSimpleName() + " on the class."); + + ELBeanName.class.getSimpleName() + " on the class.", (deferred.size() == 1 ? deferred.get(0) : null)); } From c70620e64490ea44d2e32debcc8565be354df152 Mon Sep 17 00:00:00 2001 From: "Lincoln Baxter, III" Date: Tue, 21 Mar 2023 17:24:20 -0400 Subject: [PATCH 2/3] feat(spring): support Spring Boot WebApplicationContext initialization and lookup --- .../spring/SpringBeanNameResolver.java | 25 +++++++----- .../SpringExpressionLanguageProvider.java | 8 ++++ .../rewrite/spring/SpringServiceEnricher.java | 32 ++++++++++++++- .../rewrite/spring/SpringServiceLocator.java | 40 ++++++++++++++++--- ...cpsoft.rewrite.servlet.spi.ContextListener | 2 + 5 files changed, 89 insertions(+), 18 deletions(-) create mode 100644 integration-spring/src/main/resources/META-INF/services/org.ocpsoft.rewrite.servlet.spi.ContextListener diff --git a/integration-spring/src/main/java/org/ocpsoft/rewrite/spring/SpringBeanNameResolver.java b/integration-spring/src/main/java/org/ocpsoft/rewrite/spring/SpringBeanNameResolver.java index 418b72a9b..1103ed355 100644 --- a/integration-spring/src/main/java/org/ocpsoft/rewrite/spring/SpringBeanNameResolver.java +++ b/integration-spring/src/main/java/org/ocpsoft/rewrite/spring/SpringBeanNameResolver.java @@ -16,12 +16,12 @@ package org.ocpsoft.rewrite.spring; import java.util.HashSet; -import java.util.Map; import java.util.Set; import org.ocpsoft.logging.Logger; import org.ocpsoft.rewrite.el.spi.BeanNameResolver; import org.springframework.beans.factory.ListableBeanFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.context.ContextLoader; import org.springframework.web.context.WebApplicationContext; @@ -29,24 +29,28 @@ * {@link BeanNameResolver} implementation for Spring. * * @author Christian Kaltepoth + * @author Lincoln Baxter, III */ public class SpringBeanNameResolver implements BeanNameResolver { private final Logger log = Logger.getLogger(SpringBeanNameResolver.class); + @Autowired + private WebApplicationContext applicationContext; + @Override public String getBeanName(Class clazz) { - - // try to obtain the WebApplicationContext using ContextLoader - WebApplicationContext context = ContextLoader.getCurrentWebApplicationContext(); - if (context == null) { - throw new IllegalStateException("Unable to get current WebApplicationContext"); + if (applicationContext == null) { + applicationContext = ContextLoader.getCurrentWebApplicationContext(); + if (applicationContext == null) { + throw new IllegalStateException("Unable to get current WebApplicationContext"); + } } // obtain a map of bean names - Set beanNames = resolveBeanNames(context, clazz); + Set beanNames = resolveBeanNames(applicationContext, clazz); // no beans of that type, nothing we can do if (beanNames == null || beanNames.size() == 0) { @@ -76,15 +80,14 @@ private Set resolveBeanNames(ListableBeanFactory beanFactory, Class c final Set result = new HashSet(); - Map beanMap = beanFactory.getBeansOfType(clazz); - if (beanMap != null) { - for (String name : beanMap.keySet()) { + String[] names = beanFactory.getBeanNamesForType(clazz); + if (names != null) { + for (String name : names) { if (name != null && !name.startsWith("scopedTarget.")) { result.add(name); } } } - return result; } diff --git a/integration-spring/src/main/java/org/ocpsoft/rewrite/spring/SpringExpressionLanguageProvider.java b/integration-spring/src/main/java/org/ocpsoft/rewrite/spring/SpringExpressionLanguageProvider.java index 0fa5916db..0c27ee045 100644 --- a/integration-spring/src/main/java/org/ocpsoft/rewrite/spring/SpringExpressionLanguageProvider.java +++ b/integration-spring/src/main/java/org/ocpsoft/rewrite/spring/SpringExpressionLanguageProvider.java @@ -33,6 +33,7 @@ import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.expression.spel.support.StandardTypeConverter; import org.springframework.expression.spel.support.StandardTypeLocator; +import org.springframework.web.context.ContextLoader; import org.springframework.web.context.WebApplicationContext; /** @@ -136,6 +137,13 @@ public EvaluationContext getEvaluationContext() // we need a ConfigurableBeanFactory to build the BeanExpressionContext ConfigurableBeanFactory beanFactory = null; + if (applicationContext == null) { + applicationContext = ContextLoader.getCurrentWebApplicationContext(); + if (applicationContext == null) { + throw new IllegalStateException("Unable to get current WebApplicationContext"); + } + } + // the WebApplicationContext MAY implement ConfigurableBeanFactory if (applicationContext instanceof ConfigurableBeanFactory) { beanFactory = (ConfigurableBeanFactory) applicationContext; diff --git a/integration-spring/src/main/java/org/ocpsoft/rewrite/spring/SpringServiceEnricher.java b/integration-spring/src/main/java/org/ocpsoft/rewrite/spring/SpringServiceEnricher.java index 0c29acaf6..96fed10b0 100644 --- a/integration-spring/src/main/java/org/ocpsoft/rewrite/spring/SpringServiceEnricher.java +++ b/integration-spring/src/main/java/org/ocpsoft/rewrite/spring/SpringServiceEnricher.java @@ -18,8 +18,12 @@ import java.util.ArrayList; import java.util.Collection; +import javax.servlet.ServletContext; +import javax.servlet.ServletContextEvent; + import org.ocpsoft.common.spi.ServiceEnricher; import org.ocpsoft.logging.Logger; +import org.ocpsoft.rewrite.servlet.spi.ContextListener; import org.springframework.web.context.support.SpringBeanAutowiringSupport; /** @@ -27,15 +31,21 @@ * * @author Christian Kaltepoth */ -public class SpringServiceEnricher implements ServiceEnricher +public class SpringServiceEnricher implements ServiceEnricher, ContextListener { private final Logger log = Logger.getLogger(SpringServiceEnricher.class); + private static ServletContext context; @Override public void enrich(final T service) { - SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(service); + if (context != null) { + SpringBeanAutowiringSupport.processInjectionBasedOnServletContext(service, context); + } + else { + SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(service); + } if (log.isDebugEnabled()) log.debug("Enriched instance of service [" + service.getClass().getName() + "]"); @@ -48,4 +58,22 @@ public Collection produce(final Class type) return new ArrayList(); } + @Override + public void contextInitialized(ServletContextEvent event) + { + context = event.getServletContext(); + } + + @Override + public void contextDestroyed(ServletContextEvent event) + { + context = null; + } + + @Override + public int priority() + { + return 0; + } + } diff --git a/integration-spring/src/main/java/org/ocpsoft/rewrite/spring/SpringServiceLocator.java b/integration-spring/src/main/java/org/ocpsoft/rewrite/spring/SpringServiceLocator.java index 62f43652b..8c81eddf7 100644 --- a/integration-spring/src/main/java/org/ocpsoft/rewrite/spring/SpringServiceLocator.java +++ b/integration-spring/src/main/java/org/ocpsoft/rewrite/spring/SpringServiceLocator.java @@ -20,17 +20,24 @@ import java.util.Map; import java.util.Set; +import javax.servlet.ServletContext; +import javax.servlet.ServletContextEvent; + import org.ocpsoft.common.spi.ServiceLocator; +import org.ocpsoft.rewrite.servlet.spi.ContextListener; import org.springframework.web.context.ContextLoader; import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.context.support.WebApplicationContextUtils; /** * {@link ServiceLocator} implementation for Spring. * * @author Christian Kaltepoth + * @author Lincoln Baxter, III */ -public class SpringServiceLocator implements ServiceLocator +public class SpringServiceLocator implements ServiceLocator, ContextListener { + private static ServletContext servletContext; @Override @SuppressWarnings("unchecked") @@ -38,14 +45,19 @@ public Collection> locate(Class clazz) { Set> result = new LinkedHashSet>(); - // use the Spring API to obtain the WebApplicationContext - WebApplicationContext context = ContextLoader.getCurrentWebApplicationContext(); + WebApplicationContext applicationContext = null; + if (servletContext != null) { + applicationContext = WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext); + } + else { + applicationContext = ContextLoader.getCurrentWebApplicationContext(); + } // may be null if Spring hasn't started yet - if (context != null) { + if (applicationContext != null) { // ask spring about SPI implementations - Map beans = context.getBeansOfType(clazz); + Map beans = applicationContext.getBeansOfType(clazz); // add the implementations Class objects to the result set for (T type : beans.values()) { @@ -57,4 +69,22 @@ public Collection> locate(Class clazz) return result; } + @Override + public void contextInitialized(ServletContextEvent event) + { + servletContext = event.getServletContext(); + } + + @Override + public void contextDestroyed(ServletContextEvent event) + { + servletContext = null; + } + + @Override + public int priority() + { + return 0; + } + } diff --git a/integration-spring/src/main/resources/META-INF/services/org.ocpsoft.rewrite.servlet.spi.ContextListener b/integration-spring/src/main/resources/META-INF/services/org.ocpsoft.rewrite.servlet.spi.ContextListener new file mode 100644 index 000000000..677177826 --- /dev/null +++ b/integration-spring/src/main/resources/META-INF/services/org.ocpsoft.rewrite.servlet.spi.ContextListener @@ -0,0 +1,2 @@ +org.ocpsoft.rewrite.spring.SpringServiceEnricher +org.ocpsoft.rewrite.spring.SpringServiceLocator \ No newline at end of file From 5e45cfc93cbd07725ac1fbe7cf9b79b521b4d4e1 Mon Sep 17 00:00:00 2001 From: "Lincoln Baxter, III" Date: Wed, 22 Mar 2023 14:06:22 -0400 Subject: [PATCH 3/3] fix(spring): implement thread safety for ServletContext resolution & lookup --- .../rewrite/spring/SpringServiceEnricher.java | 24 +---- .../rewrite/spring/SpringServiceLocator.java | 25 +----- .../spring/SpringServletContextLoader.java | 87 +++++++++++++++++++ ...cpsoft.rewrite.servlet.spi.ContextListener | 3 +- ...cpsoft.rewrite.servlet.spi.RequestListener | 1 + 5 files changed, 93 insertions(+), 47 deletions(-) create mode 100644 integration-spring/src/main/java/org/ocpsoft/rewrite/spring/SpringServletContextLoader.java create mode 100644 integration-spring/src/main/resources/META-INF/services/org.ocpsoft.rewrite.servlet.spi.RequestListener diff --git a/integration-spring/src/main/java/org/ocpsoft/rewrite/spring/SpringServiceEnricher.java b/integration-spring/src/main/java/org/ocpsoft/rewrite/spring/SpringServiceEnricher.java index 96fed10b0..2f4ff3377 100644 --- a/integration-spring/src/main/java/org/ocpsoft/rewrite/spring/SpringServiceEnricher.java +++ b/integration-spring/src/main/java/org/ocpsoft/rewrite/spring/SpringServiceEnricher.java @@ -19,11 +19,9 @@ import java.util.Collection; import javax.servlet.ServletContext; -import javax.servlet.ServletContextEvent; import org.ocpsoft.common.spi.ServiceEnricher; import org.ocpsoft.logging.Logger; -import org.ocpsoft.rewrite.servlet.spi.ContextListener; import org.springframework.web.context.support.SpringBeanAutowiringSupport; /** @@ -31,15 +29,15 @@ * * @author Christian Kaltepoth */ -public class SpringServiceEnricher implements ServiceEnricher, ContextListener +public class SpringServiceEnricher implements ServiceEnricher { private final Logger log = Logger.getLogger(SpringServiceEnricher.class); - private static ServletContext context; @Override public void enrich(final T service) { + ServletContext context = SpringServletContextLoader.getCurrentServletContext(); if (context != null) { SpringBeanAutowiringSupport.processInjectionBasedOnServletContext(service, context); } @@ -58,22 +56,4 @@ public Collection produce(final Class type) return new ArrayList(); } - @Override - public void contextInitialized(ServletContextEvent event) - { - context = event.getServletContext(); - } - - @Override - public void contextDestroyed(ServletContextEvent event) - { - context = null; - } - - @Override - public int priority() - { - return 0; - } - } diff --git a/integration-spring/src/main/java/org/ocpsoft/rewrite/spring/SpringServiceLocator.java b/integration-spring/src/main/java/org/ocpsoft/rewrite/spring/SpringServiceLocator.java index 8c81eddf7..0644a999d 100644 --- a/integration-spring/src/main/java/org/ocpsoft/rewrite/spring/SpringServiceLocator.java +++ b/integration-spring/src/main/java/org/ocpsoft/rewrite/spring/SpringServiceLocator.java @@ -21,10 +21,8 @@ import java.util.Set; import javax.servlet.ServletContext; -import javax.servlet.ServletContextEvent; import org.ocpsoft.common.spi.ServiceLocator; -import org.ocpsoft.rewrite.servlet.spi.ContextListener; import org.springframework.web.context.ContextLoader; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.support.WebApplicationContextUtils; @@ -35,16 +33,15 @@ * @author Christian Kaltepoth * @author Lincoln Baxter, III */ -public class SpringServiceLocator implements ServiceLocator, ContextListener +public class SpringServiceLocator implements ServiceLocator { - private static ServletContext servletContext; - @Override @SuppressWarnings("unchecked") public Collection> locate(Class clazz) { Set> result = new LinkedHashSet>(); + ServletContext servletContext = SpringServletContextLoader.getCurrentServletContext(); WebApplicationContext applicationContext = null; if (servletContext != null) { applicationContext = WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext); @@ -69,22 +66,4 @@ public Collection> locate(Class clazz) return result; } - @Override - public void contextInitialized(ServletContextEvent event) - { - servletContext = event.getServletContext(); - } - - @Override - public void contextDestroyed(ServletContextEvent event) - { - servletContext = null; - } - - @Override - public int priority() - { - return 0; - } - } diff --git a/integration-spring/src/main/java/org/ocpsoft/rewrite/spring/SpringServletContextLoader.java b/integration-spring/src/main/java/org/ocpsoft/rewrite/spring/SpringServletContextLoader.java new file mode 100644 index 000000000..08374113b --- /dev/null +++ b/integration-spring/src/main/java/org/ocpsoft/rewrite/spring/SpringServletContextLoader.java @@ -0,0 +1,87 @@ +/* + * Copyright 2011 Lincoln Baxter, III + * + * 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 org.ocpsoft.rewrite.spring; + +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; + +import javax.servlet.ServletContext; +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletRequestEvent; + +import org.ocpsoft.rewrite.servlet.spi.ContextListener; +import org.ocpsoft.rewrite.servlet.spi.RequestListener; + +/** + * Thread-safe {@link ServletContext} loader implementation for Spring. + * + * @author Lincoln Baxter, III + */ +public class SpringServletContextLoader implements ContextListener, RequestListener +{ + private static final Map contextMap = new ConcurrentHashMap<>(1); + + @Override + public void contextInitialized(ServletContextEvent event) + { + ServletContext servletContext = event.getServletContext(); + contextMap.put(Thread.currentThread().getContextClassLoader(), servletContext); + } + + @Override + public void contextDestroyed(ServletContextEvent event) + { + removeContext(event.getServletContext()); + } + + @Override + public void requestInitialized(ServletRequestEvent event) + { + ServletContext servletContext = event.getServletContext(); + contextMap.put(Thread.currentThread().getContextClassLoader(), servletContext); + } + + @Override + public void requestDestroyed(ServletRequestEvent event) + { + removeContext(event.getServletContext()); + + } + + private static void removeContext(ServletContext context) + { + if (contextMap.containsValue(context)) { + for (Entry entry : contextMap.entrySet()) { + if (entry.getValue() == context) { + contextMap.remove(entry.getKey()); + } + } + } + } + + public static ServletContext getCurrentServletContext() + { + return contextMap.get(Thread.currentThread().getContextClassLoader()); + } + + @Override + public int priority() + { + return 0; + } + +} diff --git a/integration-spring/src/main/resources/META-INF/services/org.ocpsoft.rewrite.servlet.spi.ContextListener b/integration-spring/src/main/resources/META-INF/services/org.ocpsoft.rewrite.servlet.spi.ContextListener index 677177826..8e610f7fe 100644 --- a/integration-spring/src/main/resources/META-INF/services/org.ocpsoft.rewrite.servlet.spi.ContextListener +++ b/integration-spring/src/main/resources/META-INF/services/org.ocpsoft.rewrite.servlet.spi.ContextListener @@ -1,2 +1 @@ -org.ocpsoft.rewrite.spring.SpringServiceEnricher -org.ocpsoft.rewrite.spring.SpringServiceLocator \ No newline at end of file +org.ocpsoft.rewrite.spring.SpringServletContextLoader \ No newline at end of file diff --git a/integration-spring/src/main/resources/META-INF/services/org.ocpsoft.rewrite.servlet.spi.RequestListener b/integration-spring/src/main/resources/META-INF/services/org.ocpsoft.rewrite.servlet.spi.RequestListener new file mode 100644 index 000000000..8e610f7fe --- /dev/null +++ b/integration-spring/src/main/resources/META-INF/services/org.ocpsoft.rewrite.servlet.spi.RequestListener @@ -0,0 +1 @@ +org.ocpsoft.rewrite.spring.SpringServletContextLoader \ No newline at end of file