This is an automated email from the ASF dual-hosted git repository. jlmonteiro pushed a commit to branch owb_2.0.x in repository https://gitbox.apache.org/repos/asf/openwebbeans.git
commit ab45ce695d8e87191132db0a6f2f4d60e5abb7e1 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 | 120 +++-- .../webbeans/portable/AnnotatedTypeImpl.java | 2 +- .../org/apache/webbeans/util/WebBeansUtil.java | 8 + .../TrackingAnnotatedTypeConfiguratorImplTest.java | 251 ++++++++++ .../InterceptorDecoratorProxyFactoryTest.java | 74 ++- 6 files changed, 944 insertions(+), 33 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..4826c199d --- /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 javax.enterprise.inject.spi.AnnotatedParameter; +import javax.enterprise.inject.spi.AnnotatedType; +import javax.enterprise.inject.spi.AnnotatedConstructor; +import javax.enterprise.inject.spi.AnnotatedField; +import javax.enterprise.inject.spi.AnnotatedMethod; +import javax.enterprise.inject.spi.configurator.AnnotatedConstructorConfigurator; +import javax.enterprise.inject.spi.configurator.AnnotatedFieldConfigurator; +import javax.enterprise.inject.spi.configurator.AnnotatedMethodConfigurator; +import javax.enterprise.inject.spi.configurator.AnnotatedParameterConfigurator; +import javax.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 3064902d3..0262f925f 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 javax.enterprise.inject.spi.AnnotatedType; import javax.enterprise.inject.spi.InterceptionFactory; -import javax.enterprise.inject.spi.Interceptor; import javax.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,42 @@ 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.createProxyClass(interceptorInfo, newAnnotatedType, classLoader); + final ClassLoader classLoader = ofNullable(originalInstance.getClass().getClassLoader()) + .orElseGet(WebBeansUtil::getCurrentClassLoader); - Map<Interceptor<?>,Object> interceptorInstances = context.getInterceptorResolutionService() - .createInterceptorInstances(interceptorInfo, creationalContext); + AnnotatedType<T> newAnnotatedType = configurator == null ? at : configurator.getNewAnnotatedType(); + newAnnotatedType.getTypeClosure(); // make sure the toString bellow is accurate + String 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(); + InterceptorResolutionService interceptorResolutionService = context.getInterceptorResolutionService(); + InterceptionFactoryCacheEntry 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); + Map<javax.enterprise.inject.spi.Interceptor<?>, Object> interceptorInstances = interceptorResolutionService + .createInterceptorInstances(cache.interceptorInfo, creationalContext); + Map<java.lang.reflect.Method, java.util.List<javax.enterprise.inject.spi.Interceptor<?>>> 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 +138,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) + { + InterceptionFactoryCacheEntry 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 1610b745b..e26ec7fec 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/util/WebBeansUtil.java b/webbeans-impl/src/main/java/org/apache/webbeans/util/WebBeansUtil.java index cbd36388a..b30ed3ed5 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 @@ -55,6 +55,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; @@ -173,6 +174,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; @@ -1762,6 +1765,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..23281de3f --- /dev/null +++ b/webbeans-impl/src/test/java/org/apache/webbeans/configurator/TrackingAnnotatedTypeConfiguratorImplTest.java @@ -0,0 +1,251 @@ +/* + * 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 org.apache.webbeans.config.WebBeansContext; +import org.apache.webbeans.test.AbstractUnitTest; +import org.junit.Test; + +import javax.enterprise.inject.spi.AnnotatedConstructor; +import javax.enterprise.inject.spi.AnnotatedField; +import javax.enterprise.inject.spi.AnnotatedMethod; +import javax.enterprise.inject.spi.AnnotatedParameter; +import javax.enterprise.inject.spi.AnnotatedType; +import javax.enterprise.inject.spi.configurator.AnnotatedConstructorConfigurator; +import javax.enterprise.inject.spi.configurator.AnnotatedFieldConfigurator; +import javax.enterprise.inject.spi.configurator.AnnotatedMethodConfigurator; +import javax.enterprise.inject.spi.configurator.AnnotatedParameterConfigurator; +import java.lang.annotation.Annotation; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +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() { + Set<AnnotatedFieldConfigurator<? super MyBean>> annotatedFieldConfigurators = new HashSet<>(); + annotatedFieldConfigurators.add(new FullStubFieldConfigurator<>()); + return annotatedFieldConfigurators; + } + + @Override + public Set<AnnotatedMethodConfigurator<? super MyBean>> methods() { + Set<AnnotatedMethodConfigurator<? super MyBean>> annotatedMethodConfigurators = new HashSet<>(); + annotatedMethodConfigurators.add(new FullStubMethodConfigurator<>()); + return annotatedMethodConfigurators; + } + + @Override + public Set<AnnotatedConstructorConfigurator<MyBean>> constructors() { + Set<AnnotatedConstructorConfigurator<MyBean>> annotatedConstructorConfigurators = new HashSet<>(); + annotatedConstructorConfigurators.add(new FullStubConstructorConfigurator<>()); + return annotatedConstructorConfigurators; + } + + @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() { + List<AnnotatedParameterConfigurator<MyBean>> annotatedParameterConfigurators = new ArrayList<>(); + annotatedParameterConfigurators.add(new FullStubParamConfigurator<>()); + return annotatedParameterConfigurators; + } + + @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() { + List<AnnotatedParameterConfigurator<MyBean>> annotatedParameterConfigurators = new ArrayList<>(); + annotatedParameterConfigurators.add(new FullStubParamConfigurator<>()); + return annotatedParameterConfigurators; + } + + @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 887342b62..b5737e58c 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 @@ -32,25 +32,52 @@ import java.util.Iterator; import java.util.List; import java.util.Set; +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.context.spi.CreationalContext; +import javax.enterprise.inject.Produces; +import javax.enterprise.inject.spi.Bean; +import javax.enterprise.inject.spi.InjectionPoint; +import javax.enterprise.inject.spi.InterceptionFactory; +import javax.enterprise.util.AnnotationLiteral; +import javax.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} @@ -58,6 +85,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 AnnotationLiteral<SimpleQualifier> simpleQualifier = new EmptyAnnotationLiteral<SimpleQualifier>(){}; + final Runnable r11 = getInstance(Runnable.class); + final Runnable r12 = getInstance(Runnable.class); + assertEquals(1, getWebBeansContext().getWebBeansUtil().getInterceptionFactoryCache().size()); + final Runnable 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 { @@ -252,4 +308,12 @@ public class InterceptorDecoratorProxyFactoryTest extends AbstractUnitTest } } } + + @Target({ METHOD }) + @Retention(RUNTIME) + @Documented + @Qualifier + public @interface SimpleQualifier + { + } }