This is an automated email from the ASF dual-hosted git repository. jlmonteiro pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/openwebbeans.git
The following commit(s) were added to refs/heads/main by this push: new 18f37f888 fix(#OWB-1450): Interceptor proxy memory leak. Add caching at an higher level 18f37f888 is described below commit 18f37f8883d079d007d23dee429faf0cd79a201f Author: Jean-Louis Monteiro <jlmonte...@tomitribe.com> AuthorDate: Wed May 14 11:51:07 2025 +0200 fix(#OWB-1450): Interceptor proxy memory leak. Add caching at an higher level --- .../TrackingAnnotatedTypeConfiguratorImpl.java | 522 +++++++++++++++++++++ .../container/InterceptionFactoryImpl.java | 119 +++-- .../webbeans/portable/AnnotatedTypeImpl.java | 2 +- .../proxy/InterceptorDecoratorProxyFactory.java | 13 - .../org/apache/webbeans/util/WebBeansUtil.java | 8 + .../TrackingAnnotatedTypeConfiguratorImplTest.java | 200 ++++++++ .../InterceptorDecoratorProxyFactoryTest.java | 80 +++- 7 files changed, 888 insertions(+), 56 deletions(-) diff --git a/webbeans-impl/src/main/java/org/apache/webbeans/configurator/TrackingAnnotatedTypeConfiguratorImpl.java b/webbeans-impl/src/main/java/org/apache/webbeans/configurator/TrackingAnnotatedTypeConfiguratorImpl.java new file mode 100644 index 000000000..73c61313e --- /dev/null +++ b/webbeans-impl/src/main/java/org/apache/webbeans/configurator/TrackingAnnotatedTypeConfiguratorImpl.java @@ -0,0 +1,522 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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 org.apache.webbeans.configurator; + +import jakarta.enterprise.inject.spi.AnnotatedParameter; +import jakarta.enterprise.inject.spi.AnnotatedType; +import jakarta.enterprise.inject.spi.AnnotatedConstructor; +import jakarta.enterprise.inject.spi.AnnotatedField; +import jakarta.enterprise.inject.spi.AnnotatedMethod; +import jakarta.enterprise.inject.spi.configurator.AnnotatedConstructorConfigurator; +import jakarta.enterprise.inject.spi.configurator.AnnotatedFieldConfigurator; +import jakarta.enterprise.inject.spi.configurator.AnnotatedMethodConfigurator; +import jakarta.enterprise.inject.spi.configurator.AnnotatedParameterConfigurator; +import jakarta.enterprise.inject.spi.configurator.AnnotatedTypeConfigurator; +import org.apache.webbeans.portable.AnnotatedTypeImpl; + +import java.lang.annotation.Annotation; +import java.util.*; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class TrackingAnnotatedTypeConfiguratorImpl<T> implements AnnotatedTypeConfigurator<T> +{ + + private final AnnotatedTypeConfiguratorImpl<T> delegate; + private final List<String> actions = new ArrayList<>(); + + public TrackingAnnotatedTypeConfiguratorImpl(final AnnotatedTypeConfiguratorImpl<T> delegate) + { + this.delegate = delegate; + } + + public String getPassivationId() + { + return actions.stream().collect(Collectors.joining(">>")); + } + + @Override + public final boolean equals(final Object o) + { + if (!(o instanceof TrackingAnnotatedTypeConfiguratorImpl)) + { + return false; + } + + final TrackingAnnotatedTypeConfiguratorImpl<?> that = (TrackingAnnotatedTypeConfiguratorImpl<?>) o; + return getPassivationId().equals(that.getPassivationId()); + } + + @Override + public int hashCode() + { + return getPassivationId().hashCode(); + } + + @Override + public AnnotatedType<T> getAnnotated() + { + return delegate.getAnnotated(); + } + + @Override + public AnnotatedTypeConfigurator<T> add(Annotation annotation) + { + actions.add("+@" + annotation.annotationType()); + delegate.add(annotation); + return this; + } + + @Override + public AnnotatedTypeConfigurator<T> remove(Predicate<Annotation> predicate) + { + delegate.remove(a -> + { + if (predicate.test((Annotation) a)) + { + actions.add("-@" + ((Annotation) a).annotationType()); + return true; + } + return false; + }); + return this; + } + + @Override + public AnnotatedTypeConfigurator<T> removeAll() + { + actions.add("--"); + delegate.removeAll(); + return this; + } + + @Override + public Set<AnnotatedMethodConfigurator<? super T>> methods() + { + return delegate.methods().stream() + .map(m -> new TrackingAnnotatedMethodConfiguratorImpl<>(m, actions)) + .collect(Collectors.toSet()); + } + + @Override + public Stream<AnnotatedMethodConfigurator<? super T>> filterMethods(Predicate<AnnotatedMethod<? super T>> predicate) + { + return delegate.filterMethods(a -> + { + if (predicate.test(a)) + { + actions.add("-m@" + a); + return true; + } + return false; + }); + } + + @Override + public Set<AnnotatedFieldConfigurator<? super T>> fields() + { + return delegate.fields().stream() + .map(f -> new TrackingAnnotatedFieldConfiguratorImpl<>(f, actions)) + .collect(Collectors.toSet()); + } + + @Override + public Stream<AnnotatedFieldConfigurator<? super T>> filterFields(Predicate<AnnotatedField<? super T>> predicate) + { + return delegate.filterFields(a -> + { + if (predicate.test(a)) + { + actions.add("-f@" + a); + return true; + } + return false; + }); + } + + @Override + public Set<AnnotatedConstructorConfigurator<T>> constructors() + { + return delegate.constructors().stream() + .map(c -> new TrackingAnnotatedConstructorConfiguratorImpl<>(c, actions)) + .collect(Collectors.toSet()); + } + + @Override + public Stream<AnnotatedConstructorConfigurator<T>> filterConstructors(Predicate<AnnotatedConstructor<T>> predicate) + { + return delegate.filterConstructors(a -> + { + if (predicate.test(a)) + { + actions.add("-c@" + a); + return true; + } + return false; + }); + } + + + public AnnotatedTypeImpl<T> getNewAnnotatedType() + { + return delegate.getNewAnnotatedType(); + } + + public static class TrackingAnnotatedFieldConfiguratorImpl<T> implements AnnotatedFieldConfigurator<T> + { + private final AnnotatedFieldConfigurator<T> delegate; + private final List<String> actions; + + public TrackingAnnotatedFieldConfiguratorImpl(final AnnotatedFieldConfigurator<T> delegate, + final List<String> actions) + { + this.delegate = delegate; + this.actions = actions; + this.actions.add("\n\t"); + } + + @Override + public final boolean equals(final Object o) + { + if (!(o instanceof TrackingAnnotatedFieldConfiguratorImpl)) + { + return false; + } + + final TrackingAnnotatedFieldConfiguratorImpl<?> that = (TrackingAnnotatedFieldConfiguratorImpl<?>) o; + return delegate.equals(that.delegate); + } + + @Override + public int hashCode() + { + return delegate.hashCode(); + } + + @Override + public String toString() + { + return "TrackingField(" + delegate + ")"; + } + + @Override + public AnnotatedField<T> getAnnotated() + { + return delegate.getAnnotated(); + } + + @Override + public AnnotatedFieldConfigurator<T> removeAll() + { + actions.add("--"); + delegate.removeAll(); + return this; + } + + @Override + public AnnotatedFieldConfigurator<T> remove(final Predicate<Annotation> predicate) + { + delegate.remove(a -> + { + if (predicate.test(a)) + { + actions.add("-@" + a); + return true; + } + return false; + }); + return this; + } + + @Override + public AnnotatedFieldConfigurator<T> add(final Annotation annotation) + { + actions.add("+@" + annotation.annotationType()); + delegate.add(annotation); + return this; + } + } + + public static class TrackingAnnotatedMethodConfiguratorImpl<T> implements AnnotatedMethodConfigurator<T> + { + private final AnnotatedMethodConfigurator<T> delegate; + private final List<String> actions; + + public TrackingAnnotatedMethodConfiguratorImpl(final AnnotatedMethodConfigurator<T> delegate, + final List<String> actions) + { + this.delegate = delegate; + this.actions = actions; + this.actions.add("\n\t"); + } + + @Override + public final boolean equals(final Object o) + { + if (!(o instanceof TrackingAnnotatedMethodConfiguratorImpl)) + { + return false; + } + + final TrackingAnnotatedMethodConfiguratorImpl<?> that = (TrackingAnnotatedMethodConfiguratorImpl<?>) o; + return delegate.equals(that.delegate); + } + + @Override + public int hashCode() + { + return delegate.hashCode(); + } + + @Override + public String toString() + { + return "TrackingMethod(" + delegate + ")"; + } + + @Override + public AnnotatedMethodConfigurator<T> add(final Annotation annotation) + { + actions.add("+@" + annotation.annotationType()); + delegate.add(annotation); + return this; + } + + @Override + public Stream<AnnotatedParameterConfigurator<T>> filterParams(final Predicate<AnnotatedParameter<T>> predicate) + { + return delegate.filterParams(a -> + { + if (predicate.test(a)) + { + actions.add("-p@" + a); + return true; + } + return false; + }); + } + + @Override + public AnnotatedMethod<T> getAnnotated() + { + return delegate.getAnnotated(); + } + + @Override + public List<AnnotatedParameterConfigurator<T>> params() + { + return delegate.params().stream() + .map(p -> new TrackingAnnotatedParameterConfiguratorImpl(p, actions)) + .map(p -> (AnnotatedParameterConfigurator<T>) p) + .collect(Collectors.toList()); + } + + @Override + public AnnotatedMethodConfigurator<T> remove(final Predicate<Annotation> predicate) + { + delegate.remove(a -> + { + if (predicate.test(a)) + { + actions.add("-@" + a.toString()); + return true; + } + return false; + }); + return this; + } + + @Override + public AnnotatedMethodConfigurator<T> removeAll() + { + actions.add("--"); + delegate.removeAll(); + return this; + } + } + + public static class TrackingAnnotatedConstructorConfiguratorImpl<T> implements AnnotatedConstructorConfigurator<T> + { + private final AnnotatedConstructorConfigurator<T> delegate; + private final List<String> actions; + + public TrackingAnnotatedConstructorConfiguratorImpl(final AnnotatedConstructorConfigurator<T> delegate, + final List<String> actions) + { + this.delegate = delegate; + this.actions = actions; + this.actions.add("\n\t"); + } + + @Override + public final boolean equals(final Object o) + { + if (!(o instanceof TrackingAnnotatedConstructorConfiguratorImpl)) + { + return false; + } + + final TrackingAnnotatedConstructorConfiguratorImpl<?> that = + (TrackingAnnotatedConstructorConfiguratorImpl<?>) o; + return delegate.equals(that.delegate); + } + + @Override + public int hashCode() + { + return delegate.hashCode(); + } + + @Override + public String toString() + { + return "TrackingConstructor(" + delegate + ")"; + } + + @Override + public AnnotatedConstructor<T> getAnnotated() + { + return delegate.getAnnotated(); + } + + @Override + public AnnotatedConstructorConfigurator<T> add(final Annotation annotation) + { + actions.add("+@" + annotation.annotationType()); + delegate.add(annotation); + return this; + } + + @Override + public AnnotatedConstructorConfigurator<T> remove(final Predicate<Annotation> predicate) + { + actions.add("-@" + predicate.toString()); + delegate.remove(predicate); + return this; + } + + @Override + public AnnotatedConstructorConfigurator<T> removeAll() + { + actions.add("--"); + delegate.removeAll(); + return this; + } + + @Override + public List<AnnotatedParameterConfigurator<T>> params() + { + return delegate.params().stream() + .map(p -> new TrackingAnnotatedParameterConfiguratorImpl(p, actions)) + .map(p -> (AnnotatedParameterConfigurator<T>) p) + .collect(Collectors.toList()); + } + + @Override + public Stream<AnnotatedParameterConfigurator<T>> filterParams(final Predicate<AnnotatedParameter<T>> predicate) + { + return delegate.filterParams(a -> + { + if (predicate.test(a)) + { + actions.add("-p@" + a.toString()); + return true; + } + return false; + }); + } + } + + public static class TrackingAnnotatedParameterConfiguratorImpl<T> implements AnnotatedParameterConfigurator<T> + { + private final AnnotatedParameterConfigurator<T> delegate; + private final List<String> actions; + + public TrackingAnnotatedParameterConfiguratorImpl(final AnnotatedParameterConfigurator<T> delegate, + final List<String> actions) + { + this.delegate = delegate; + this.actions = actions; + this.actions.add("\n\t"); + } + + @Override + public final boolean equals(final Object o) + { + if (!(o instanceof TrackingAnnotatedParameterConfiguratorImpl)) + { + return false; + } + + final TrackingAnnotatedParameterConfiguratorImpl<?> that = + (TrackingAnnotatedParameterConfiguratorImpl<?>) o; + return delegate.equals(that.delegate); + } + + @Override + public int hashCode() + { + return delegate.hashCode(); + } + + @Override + public String toString() + { + return "TrackingParameter(" + delegate + ")"; + } + + @Override + public AnnotatedParameterConfigurator<T> add(final Annotation annotation) + { + actions.add("+@" + annotation.annotationType()); + delegate.add(annotation); + return this; + } + + @Override + public AnnotatedParameter<T> getAnnotated() + { + return delegate.getAnnotated(); + } + + @Override + public AnnotatedParameterConfigurator<T> remove(final Predicate<Annotation> predicate) + { + delegate.remove(a -> + { + if (predicate.test(a)) + { + actions.add("-p@" + a.toString()); + return true; + } + return false; + }); + return this; + } + + @Override + public AnnotatedParameterConfigurator<T> removeAll() + { + actions.add("--"); + delegate.removeAll(); + return this; + } + } +} \ No newline at end of file diff --git a/webbeans-impl/src/main/java/org/apache/webbeans/container/InterceptionFactoryImpl.java b/webbeans-impl/src/main/java/org/apache/webbeans/container/InterceptionFactoryImpl.java index 15c31361a..0a11958ab 100644 --- a/webbeans-impl/src/main/java/org/apache/webbeans/container/InterceptionFactoryImpl.java +++ b/webbeans-impl/src/main/java/org/apache/webbeans/container/InterceptionFactoryImpl.java @@ -20,28 +20,30 @@ package org.apache.webbeans.container; import org.apache.webbeans.config.WebBeansContext; import org.apache.webbeans.configurator.AnnotatedTypeConfiguratorImpl; +import org.apache.webbeans.configurator.TrackingAnnotatedTypeConfiguratorImpl; import org.apache.webbeans.context.creational.CreationalContextImpl; import org.apache.webbeans.intercept.InterceptorResolutionService; -import org.apache.webbeans.portable.AnnotatedTypeImpl; import org.apache.webbeans.proxy.InterceptorDecoratorProxyFactory; import org.apache.webbeans.util.WebBeansUtil; import jakarta.enterprise.inject.spi.AnnotatedType; import jakarta.enterprise.inject.spi.InterceptionFactory; -import jakarta.enterprise.inject.spi.Interceptor; import jakarta.enterprise.inject.spi.configurator.AnnotatedTypeConfigurator; import java.lang.annotation.Annotation; -import java.lang.reflect.Method; -import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Supplier; + +import static java.util.Optional.ofNullable; public class InterceptionFactoryImpl<T> implements InterceptionFactory<T> /*todo: make it serializable*/ { private final CreationalContextImpl<T> creationalContext; - private final AnnotatedTypeConfiguratorImpl<T> configurator; private final Set<Annotation> qualifiers; private final WebBeansContext context; + private final AnnotatedType<T> at; + private TrackingAnnotatedTypeConfiguratorImpl<T> configurator; private boolean ignoreFinals; private volatile boolean called; @@ -49,9 +51,10 @@ public class InterceptionFactoryImpl<T> implements InterceptionFactory<T> /*todo Set<Annotation> qualifiers, CreationalContextImpl<T> cc) { this.context = context; - this.configurator = new AnnotatedTypeConfiguratorImpl<>(context, at); + this.configurator = null; // computed later this.qualifiers = qualifiers; this.creationalContext = cc; + this.at = at; } @Override @@ -64,6 +67,12 @@ public class InterceptionFactoryImpl<T> implements InterceptionFactory<T> /*todo @Override public AnnotatedTypeConfigurator<T> configure() { + if (configurator == null) + { + // configurator = new AnnotatedTypeConfiguratorImpl<>(context, at); + AnnotatedTypeConfiguratorImpl<T> realConfig = new AnnotatedTypeConfiguratorImpl<>(context, at); + configurator = new TrackingAnnotatedTypeConfiguratorImpl<>(realConfig); + } return configurator; } @@ -72,31 +81,41 @@ public class InterceptionFactoryImpl<T> implements InterceptionFactory<T> /*todo { check(); - ClassLoader classLoader = originalInstance.getClass().getClassLoader(); - if (classLoader == null) - { - classLoader = WebBeansUtil.getCurrentClassLoader(); - } - - InterceptorDecoratorProxyFactory factory = context.getInterceptorDecoratorProxyFactory(); - AnnotatedTypeImpl<T> newAnnotatedType = configurator.getNewAnnotatedType(); - InterceptorResolutionService.BeanInterceptorInfo interceptorInfo = - context.getInterceptorResolutionService() - .calculateInterceptorInfo(newAnnotatedType.getTypeClosure(), qualifiers, newAnnotatedType, !ignoreFinals); - Class<T> subClass = factory.getCachedProxyClass(interceptorInfo, newAnnotatedType, classLoader); + final var classLoader = ofNullable(originalInstance.getClass().getClassLoader()) + .orElseGet(WebBeansUtil::getCurrentClassLoader); - Map<Interceptor<?>,Object> interceptorInstances = context.getInterceptorResolutionService() - .createInterceptorInstances(interceptorInfo, creationalContext); + var newAnnotatedType = configurator == null ? at : configurator.getNewAnnotatedType(); + newAnnotatedType.getTypeClosure(); // make sure the toString bellow is accurate + var passivationId = InterceptionFactory.class.getName() + ">>" + newAnnotatedType + "<<" + ignoreFinals; - Map<Method, List<Interceptor<?>>> methodInterceptors = - context.getInterceptorResolutionService().createMethodInterceptors(interceptorInfo); + // if configure() has not been called, we need to create a new configurator with the muted annotated type + if (configurator != null) // meaning app changed dynamically the annotated type configuration + { + passivationId = passivationId + ">>" + configurator.getPassivationId(); + } - // this is a good question actually, should we even support it? - String passivationId = InterceptionFactory.class.getName() + ">>" + newAnnotatedType.toString(); + var interceptorResolutionService = context.getInterceptorResolutionService(); + var cache = context + .getWebBeansUtil() + .getInterceptionFactoryCache() + .computeIfAbsent(passivationId, () -> { + InterceptorResolutionService.BeanInterceptorInfo interceptorInfo = + interceptorResolutionService + .calculateInterceptorInfo(newAnnotatedType.getTypeClosure(), qualifiers, newAnnotatedType, !ignoreFinals); + InterceptorDecoratorProxyFactory factory = context.getInterceptorDecoratorProxyFactory(); + return new InterceptionFactoryCacheEntry( + factory.createProxyClass(interceptorInfo, newAnnotatedType, classLoader), + interceptorInfo); + }); - return context.getInterceptorResolutionService().createProxiedInstance( - originalInstance, creationalContext, creationalContext, interceptorInfo, subClass, - methodInterceptors, passivationId, interceptorInstances, c -> false, (a, d) -> d); + var interceptorInstances = interceptorResolutionService + .createInterceptorInstances(cache.interceptorInfo, creationalContext); + var methodInterceptors = interceptorResolutionService.createMethodInterceptors(cache.interceptorInfo); + return interceptorResolutionService.createProxiedInstance( + originalInstance, creationalContext, creationalContext, cache.interceptorInfo, + (Class<? extends T>) cache.proxyClass, + methodInterceptors, passivationId, interceptorInstances, + c -> false, (a, d) -> d); } private void check() @@ -118,4 +137,50 @@ public class InterceptionFactoryImpl<T> implements InterceptionFactory<T> /*todo throw new IllegalStateException("createInterceptedInstance() can be called only once"); } } + + public static class InterceptionFactoryCache + { + private final Map<String, InterceptionFactoryCacheEntry> cache = new ConcurrentHashMap<>(); + + private InterceptionFactoryCacheEntry computeIfAbsent( + final String interceptionFactoryCacheKey, final Supplier<InterceptionFactoryCacheEntry> compute) + { + var entry = cache.get(interceptionFactoryCacheKey); + if (entry == null) + { + // we do not want to create twice a proxy class, + // "bottleneck" but quickly cached + // so "ok"ish + synchronized (this) + { + entry = cache.get(interceptionFactoryCacheKey); + if (entry == null) + { + entry = compute.get(); + cache.putIfAbsent(interceptionFactoryCacheKey, entry); + } + } + } + return entry; + } + + public int size() + { + return cache.size(); + } + } + + private static class InterceptionFactoryCacheEntry + { + private final Class<?> proxyClass; + private final InterceptorResolutionService.BeanInterceptorInfo interceptorInfo; + + private InterceptionFactoryCacheEntry( + final Class<?> proxyClass, + final InterceptorResolutionService.BeanInterceptorInfo interceptorInfo) + { + this.proxyClass = proxyClass; + this.interceptorInfo = interceptorInfo; + } + } } diff --git a/webbeans-impl/src/main/java/org/apache/webbeans/portable/AnnotatedTypeImpl.java b/webbeans-impl/src/main/java/org/apache/webbeans/portable/AnnotatedTypeImpl.java index 6a68db8de..aab575921 100644 --- a/webbeans-impl/src/main/java/org/apache/webbeans/portable/AnnotatedTypeImpl.java +++ b/webbeans-impl/src/main/java/org/apache/webbeans/portable/AnnotatedTypeImpl.java @@ -209,7 +209,7 @@ public class AnnotatedTypeImpl<X> @Override public int hashCode() // enough, no need to create a hashcode from attributes { - return super.hashCode(); + return getJavaClass().hashCode(); } private State getState() diff --git a/webbeans-impl/src/main/java/org/apache/webbeans/proxy/InterceptorDecoratorProxyFactory.java b/webbeans-impl/src/main/java/org/apache/webbeans/proxy/InterceptorDecoratorProxyFactory.java index 8dfedfaea..8defa2279 100644 --- a/webbeans-impl/src/main/java/org/apache/webbeans/proxy/InterceptorDecoratorProxyFactory.java +++ b/webbeans-impl/src/main/java/org/apache/webbeans/proxy/InterceptorDecoratorProxyFactory.java @@ -67,7 +67,6 @@ public class InterceptorDecoratorProxyFactory extends AbstractProxyFactory * We need this to prevent filling up the ClassLoaders by */ private ConcurrentMap<Bean<?>, Class<?>> cachedProxyClasses = new ConcurrentHashMap<>(); - private ConcurrentMap<AnnotatedType<?>, Class<?>> cachedProxyClassesByAt = new ConcurrentHashMap<>(); public InterceptorDecoratorProxyFactory(WebBeansContext webBeansContext) @@ -191,7 +190,6 @@ public class InterceptorDecoratorProxyFactory extends AbstractProxyFactory Class<T> proxyClass = createProxyClass( classLoader, at.getJavaClass(), intercepted.toArray(new Method[intercepted.size()]), others.toArray(new Method[others.size()])); - cachedProxyClassesByAt.put(at, proxyClass); return proxyClass; } @@ -221,17 +219,6 @@ public class InterceptorDecoratorProxyFactory extends AbstractProxyFactory return clazz; } - public <T> Class<T> getCachedProxyClass(InterceptorResolutionService.BeanInterceptorInfo interceptorInfo, - AnnotatedType<T> at, ClassLoader classLoader) - { - Class<T> value = (Class<T>) cachedProxyClassesByAt.get(at); - if (value == null) - { - value = createProxyClass(interceptorInfo, at, classLoader); - } - return value; - } - public <T> Class<T> getCachedProxyClass(Bean<T> bean) { return (Class<T>) cachedProxyClasses.get(bean); diff --git a/webbeans-impl/src/main/java/org/apache/webbeans/util/WebBeansUtil.java b/webbeans-impl/src/main/java/org/apache/webbeans/util/WebBeansUtil.java index 911b6848a..25c92c2fa 100644 --- a/webbeans-impl/src/main/java/org/apache/webbeans/util/WebBeansUtil.java +++ b/webbeans-impl/src/main/java/org/apache/webbeans/util/WebBeansUtil.java @@ -52,6 +52,7 @@ import org.apache.webbeans.config.OwbWildcardTypeImpl; import org.apache.webbeans.config.WebBeansContext; import org.apache.webbeans.container.AnnotatedTypeWrapper; import org.apache.webbeans.container.InjectionResolver; +import org.apache.webbeans.container.InterceptionFactoryImpl; import org.apache.webbeans.context.control.ActivateRequestContextInterceptorBean; import org.apache.webbeans.context.control.RequestContextControllerBean; import org.apache.webbeans.exception.WebBeansConfigurationException; @@ -168,6 +169,8 @@ public final class WebBeansUtil private final ConcurrentMap<EventCacheKey, Boolean> validEventType = new ConcurrentHashMap<>(); private final ConcurrentMap<Type, Boolean> notContainerEvents = new ConcurrentHashMap<>(); + private final InterceptionFactoryImpl.InterceptionFactoryCache interceptionFactoryCache = new InterceptionFactoryImpl.InterceptionFactoryCache(); + private InstanceBean instanceBean; private EventBean eventBean; @@ -1747,6 +1750,11 @@ public final class WebBeansUtil } } + public InterceptionFactoryImpl.InterceptionFactoryCache getInterceptionFactoryCache() + { + return interceptionFactoryCache; + } + public InterceptionFactoryBean getInterceptionFactoryBean() { return new InterceptionFactoryBean(webBeansContext); diff --git a/webbeans-impl/src/test/java/org/apache/webbeans/configurator/TrackingAnnotatedTypeConfiguratorImplTest.java b/webbeans-impl/src/test/java/org/apache/webbeans/configurator/TrackingAnnotatedTypeConfiguratorImplTest.java new file mode 100644 index 000000000..9361fce44 --- /dev/null +++ b/webbeans-impl/src/test/java/org/apache/webbeans/configurator/TrackingAnnotatedTypeConfiguratorImplTest.java @@ -0,0 +1,200 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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 org.apache.webbeans.configurator; + +import jakarta.enterprise.inject.spi.*; +import jakarta.enterprise.inject.spi.configurator.*; +import org.apache.webbeans.config.WebBeansContext; +import org.apache.webbeans.portable.AnnotatedTypeImpl; +import org.apache.webbeans.test.AbstractUnitTest; +import org.apache.webbeans.util.WebBeansUtil; +import org.junit.Test; + +import java.lang.annotation.Annotation; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.*; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static org.junit.Assert.assertTrue; + + +public class TrackingAnnotatedTypeConfiguratorImplTest extends AbstractUnitTest +{ + + @Test + public void shouldTrackAllConfiguratorActions() + { + AnnotatedTypeConfiguratorImpl<MyBean> base = new FullStubTypeConfigurator<>(); + + TrackingAnnotatedTypeConfiguratorImpl<MyBean> tracking = new TrackingAnnotatedTypeConfiguratorImpl<>(base); + + tracking.add(getDummyAnn()); + tracking.remove(a -> a.annotationType().equals(DummyAnn.class)); + tracking.removeAll(); + + tracking.fields().forEach(f -> { + f.add(getDummyAnn()); + f.remove(a -> a.annotationType().equals(DummyAnn.class)); + f.removeAll(); + }); + + tracking.methods().forEach(m -> { + m.add(getDummyAnn()); + m.remove(a -> a.annotationType().equals(DummyAnn.class)); + m.removeAll(); + + m.params().forEach(p -> { + p.add(getDummyAnn()); + p.remove(a -> a.annotationType().equals(DummyAnn.class)); + p.removeAll(); + }); + }); + + tracking.constructors().forEach(c -> { + c.add(getDummyAnn()); + c.remove(a -> a.annotationType().equals(DummyAnn.class)); + c.removeAll(); + + c.params().forEach(p -> { + p.add(getDummyAnn()); + p.remove(a -> a.annotationType().equals(DummyAnn.class)); + p.removeAll(); + }); + }); + + tracking.filterFields(f -> true).collect(Collectors.toList()); + tracking.filterMethods(m -> true).collect(Collectors.toList()); + tracking.filterConstructors(c -> true).collect(Collectors.toList()); + + final String passivationId = tracking.getPassivationId(); + + assertTrue(passivationId.contains("+@interface " + DummyAnn.class.getName())); + assertTrue(passivationId.contains("--")); // at least one removeAll() + assertTrue(passivationId.contains("-@")); // from any predicate matched removal + assertTrue(passivationId.contains("-f@")); // filterFields + assertTrue(passivationId.contains("-m@")); // filterMethods + assertTrue(passivationId.contains("-c@")); // filterConstructors + assertTrue(passivationId.contains("-p@")); // param filter + } + + public static Annotation getDummyAnn() { + return WithDummyAnn.class.getAnnotation(DummyAnn.class); + } + + @Retention(RetentionPolicy.RUNTIME) + public @interface DummyAnn {} + + @DummyAnn + static class WithDummyAnn {} + + public static class MyBean { + @DummyAnn + public String field; + + @DummyAnn + public void method(@DummyAnn String param) {} + + @DummyAnn + public MyBean() {} + } + + public static class FullStubTypeConfigurator<T> extends AnnotatedTypeConfiguratorImpl<MyBean> { + public FullStubTypeConfigurator() { super(WebBeansContext.currentInstance(), + WebBeansContext.currentInstance() + .getBeanManagerImpl() + .createAnnotatedType(MyBean.class)); } + + @Override + public Set<AnnotatedFieldConfigurator<? super MyBean>> fields() { + return Set.of(new FullStubFieldConfigurator<>()); + } + + @Override + public Set<AnnotatedMethodConfigurator<? super MyBean>> methods() { + return Set.of(new FullStubMethodConfigurator<>()); + } + + @Override + public Set<AnnotatedConstructorConfigurator<MyBean>> constructors() { + return Set.of(new FullStubConstructorConfigurator<>()); + } + + @Override + public Stream<AnnotatedFieldConfigurator<? super MyBean>> filterFields(Predicate<AnnotatedField<? super MyBean>> p) { + p.test(null); + return fields().stream(); + } + + @Override + public Stream<AnnotatedMethodConfigurator<? super MyBean>> filterMethods(Predicate<AnnotatedMethod<? super MyBean>> p) { + p.test(null); + return methods().stream(); + } + + @Override + public Stream<AnnotatedConstructorConfigurator<MyBean>> filterConstructors(Predicate<AnnotatedConstructor<MyBean>> p) { + p.test(null); + return constructors().stream(); + } + + @Override + public AnnotatedType<MyBean> getAnnotated() { + return super.getAnnotated(); + } + } + + public static class FullStubFieldConfigurator<T> implements AnnotatedFieldConfigurator<MyBean> { + @Override public AnnotatedField<MyBean> getAnnotated() { return null; } + @Override public AnnotatedFieldConfigurator<MyBean> removeAll() { return this; } + @Override public AnnotatedFieldConfigurator<MyBean> remove(Predicate<Annotation> predicate) { predicate.test(getDummyAnn()); return this; } + @Override public AnnotatedFieldConfigurator<MyBean> add(Annotation annotation) { return this; } + } + + public static class FullStubMethodConfigurator<T> implements AnnotatedMethodConfigurator<MyBean> { + @Override public AnnotatedMethodConfigurator<MyBean> add(Annotation annotation) { return this; } + @Override public Stream<AnnotatedParameterConfigurator<MyBean>> filterParams(Predicate<AnnotatedParameter<MyBean>> p) { p.test(null); return params().stream(); } + @Override public AnnotatedMethod<MyBean> getAnnotated() { return null; } + @Override public List<AnnotatedParameterConfigurator<MyBean>> params() { + return List.of(new FullStubParamConfigurator<>()); + } + @Override public AnnotatedMethodConfigurator<MyBean> remove(Predicate<Annotation> predicate) { predicate.test(getDummyAnn()); return this; } + @Override public AnnotatedMethodConfigurator<MyBean> removeAll() { return this; } + } + + public static class FullStubConstructorConfigurator<T> implements AnnotatedConstructorConfigurator<MyBean> { + @Override public AnnotatedConstructor<MyBean> getAnnotated() { return null; } + @Override public AnnotatedConstructorConfigurator<MyBean> add(Annotation annotation) { return this; } + @Override public AnnotatedConstructorConfigurator<MyBean> remove(Predicate<Annotation> predicate) { predicate.test(getDummyAnn()); return this; } + @Override public AnnotatedConstructorConfigurator<MyBean> removeAll() { return this; } + @Override public List<AnnotatedParameterConfigurator<MyBean>> params() { + return List.of(new FullStubParamConfigurator<>()); + } + @Override public Stream<AnnotatedParameterConfigurator<MyBean>> filterParams(Predicate<AnnotatedParameter<MyBean>> p) { p.test(null); return params().stream(); } + } + + public static class FullStubParamConfigurator<T> implements AnnotatedParameterConfigurator<MyBean> { + @Override public AnnotatedParameterConfigurator<MyBean> add(Annotation annotation) { return this; } + @Override public AnnotatedParameter<MyBean> getAnnotated() { return null; } + @Override public AnnotatedParameterConfigurator<MyBean> remove(Predicate<Annotation> predicate) { predicate.test(getDummyAnn()); return this; } + @Override public AnnotatedParameterConfigurator<MyBean> removeAll() { return this; } + } +} \ No newline at end of file diff --git a/webbeans-impl/src/test/java/org/apache/webbeans/test/interceptors/factory/InterceptorDecoratorProxyFactoryTest.java b/webbeans-impl/src/test/java/org/apache/webbeans/test/interceptors/factory/InterceptorDecoratorProxyFactoryTest.java index dd6f8d6ff..b875aa28c 100644 --- a/webbeans-impl/src/test/java/org/apache/webbeans/test/interceptors/factory/InterceptorDecoratorProxyFactoryTest.java +++ b/webbeans-impl/src/test/java/org/apache/webbeans/test/interceptors/factory/InterceptorDecoratorProxyFactoryTest.java @@ -18,38 +18,51 @@ */ package org.apache.webbeans.test.interceptors.factory; +import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.context.spi.CreationalContext; +import jakarta.enterprise.inject.Produces; import jakarta.enterprise.inject.spi.Bean; import jakarta.enterprise.inject.spi.InjectionPoint; -import java.lang.annotation.Annotation; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Type; -import java.net.URL; -import java.net.URLClassLoader; -import java.util.ArrayList; -import java.util.List; -import java.util.Set; - +import jakarta.enterprise.inject.spi.InterceptionFactory; +import jakarta.inject.Qualifier; +import org.apache.webbeans.annotation.EmptyAnnotationLiteral; import org.apache.webbeans.config.WebBeansContext; import org.apache.webbeans.exception.WebBeansException; -import org.apache.webbeans.test.AbstractUnitTest; -import org.apache.webbeans.test.component.intercept.webbeans.TransactionalInterceptor; -import org.apache.webbeans.test.interceptors.factory.beans.ClassInterceptedClass; import org.apache.webbeans.proxy.InterceptorDecoratorProxyFactory; - import org.apache.webbeans.proxy.InterceptorHandler; import org.apache.webbeans.proxy.OwbInterceptorProxy; +import org.apache.webbeans.test.AbstractUnitTest; +import org.apache.webbeans.test.component.intercept.webbeans.TransactionalInterceptor; +import org.apache.webbeans.test.interceptors.factory.beans.ClassInterceptedClass; import org.apache.webbeans.test.interceptors.factory.beans.TonsOfMethodsInterceptedClass; -import org.apache.webbeans.util.ClassUtil; import org.apache.webbeans.test.util.CustomBaseType; import org.apache.webbeans.test.util.CustomType; import org.apache.webbeans.test.util.ExtendedSpecificClass; import org.apache.webbeans.test.util.GenericInterface; import org.apache.webbeans.test.util.SpecificClass; +import org.apache.webbeans.util.ClassUtil; import org.junit.Assert; import org.junit.Test; +import java.lang.annotation.Annotation; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Type; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertSame; + /** * Test the {@link org.apache.webbeans.proxy.InterceptorDecoratorProxyFactory} @@ -57,6 +70,35 @@ import org.junit.Test; public class InterceptorDecoratorProxyFactoryTest extends AbstractUnitTest { + @ApplicationScoped + public static class IFProducer { + @Produces + public Runnable wrap1(final InterceptionFactory<Runnable> interceptionFactory) { + return interceptionFactory.createInterceptedInstance(() -> {}); + } + + @Produces + @SimpleQualifier + public Runnable wrap2(final InterceptionFactory<Runnable> interceptionFactory) { + interceptionFactory.configure().add(new EmptyAnnotationLiteral<SimpleQualifier>(){}); + return interceptionFactory.createInterceptedInstance(() -> {}); + } + } + + @Test + public void testEnsureOneProxyPerAT() { + startContainer(IFProducer.class); + final var simpleQualifier = new EmptyAnnotationLiteral<SimpleQualifier>(){}; + final var r11 = getInstance(Runnable.class); + final var r12 = getInstance(Runnable.class); + assertEquals(1, getWebBeansContext().getWebBeansUtil().getInterceptionFactoryCache().size()); + final var r2 = getInstance(Runnable.class, simpleQualifier); + assertEquals(2, getWebBeansContext().getWebBeansUtil().getInterceptionFactoryCache().size()); + assertSame(r11.getClass(), r12.getClass()); + assertNotSame(r2.getClass(), r11.getClass()); + assertSame(getInstance(Runnable.class, simpleQualifier).getClass(), r2.getClass()); + } + @Test public void testSimpleProxyCreation() throws Exception { @@ -245,4 +287,12 @@ public class InterceptorDecoratorProxyFactoryTest extends AbstractUnitTest } } } + + @Target({ METHOD }) + @Retention(RUNTIME) + @Documented + @Qualifier + public @interface SimpleQualifier + { + } }