This is an automated email from the ASF dual-hosted git repository. rmannibucau pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/openwebbeans.git
The following commit(s) were added to refs/heads/master by this push: new 234c0fc [OWB-1325] better impl of stable names for proxies 234c0fc is described below commit 234c0fcf2015f815fd6aa2ab0c8acdf5f8574408 Author: Romain Manni-Bucau <rmannibu...@gmail.com> AuthorDate: Sun Jun 7 16:31:06 2020 +0200 [OWB-1325] better impl of stable names for proxies --- .../java/org/apache/webbeans/hash/XxHash64.java | 174 +++++++++++++++++++++ .../webbeans/proxy/AbstractProxyFactory.java | 36 +++++ .../proxy/InterceptorDecoratorProxyFactory.java | 8 +- .../webbeans/proxy/NormalScopeProxyFactory.java | 9 +- .../org/apache/webbeans/hash/XxHash64Test.java | 32 ++++ .../webbeans/test/managed/ProxyFactoryTest.java | 92 ++++++++++- 6 files changed, 340 insertions(+), 11 deletions(-) diff --git a/webbeans-impl/src/main/java/org/apache/webbeans/hash/XxHash64.java b/webbeans-impl/src/main/java/org/apache/webbeans/hash/XxHash64.java new file mode 100644 index 0000000..2f19ad2 --- /dev/null +++ b/webbeans-impl/src/main/java/org/apache/webbeans/hash/XxHash64.java @@ -0,0 +1,174 @@ +/* + * 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. + */ + +// original header +/* + * Copyright 2015 Higher Frequency Trading http://www.higherfrequencytrading.com + * + * 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.apache.webbeans.hash; + +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; + +// forked from @OpenHFT/Zero-Allocation-Hashing/blob/master/src/main/java/net/openhft/hashing/XxHash.java +// (ASFv2 license) +public final class XxHash64 +{ + private static final long PRIME64_1 = 0x9E3779B185EBCA87L; + private static final long PRIME64_2 = 0xC2B2AE3D27D4EB4FL; + private static final long PRIME64_3 = 0x165667B19E3779F9L; + private static final long PRIME64_4 = 0x85EBCA77C2b2AE63L; + private static final long PRIME64_5 = 0x27D4EB2F165667C5L; + + private XxHash64() + { + // no-op + } + + public static long apply(final String input) + { + return apply(ByteBuffer.wrap(input.getBytes(StandardCharsets.UTF_8))); + } + + public static long apply(final ByteBuffer input) + { + int length = input.remaining(); + long remaining = length; + + long hash; + int off = 0; + if (remaining >= 32) + { + long v1 = PRIME64_1 + PRIME64_2; + long v2 = PRIME64_2; + long v3 = 0; + long v4 = -PRIME64_1; + + do + { + v1 += input.getLong(off) * PRIME64_2; + v1 = Long.rotateLeft(v1, 31); + v1 *= PRIME64_1; + + v2 += input.getLong(off + 8) * PRIME64_2; + v2 = Long.rotateLeft(v2, 31); + v2 *= PRIME64_1; + + v3 += input.getLong(off + 16) * PRIME64_2; + v3 = Long.rotateLeft(v3, 31); + v3 *= PRIME64_1; + + v4 += input.getLong(off + 24) * PRIME64_2; + v4 = Long.rotateLeft(v4, 31); + v4 *= PRIME64_1; + + off += 32; + remaining -= 32; + } + while (remaining >= 32); + + hash = Long.rotateLeft(v1, 1) + + Long.rotateLeft(v2, 7) + + Long.rotateLeft(v3, 12) + + Long.rotateLeft(v4, 18); + + v1 *= PRIME64_2; + v1 = Long.rotateLeft(v1, 31); + v1 *= PRIME64_1; + hash ^= v1; + hash = hash * PRIME64_1 + PRIME64_4; + + v2 *= PRIME64_2; + v2 = Long.rotateLeft(v2, 31); + v2 *= PRIME64_1; + hash ^= v2; + hash = hash * PRIME64_1 + PRIME64_4; + + v3 *= PRIME64_2; + v3 = Long.rotateLeft(v3, 31); + v3 *= PRIME64_1; + hash ^= v3; + hash = hash * PRIME64_1 + PRIME64_4; + + v4 *= PRIME64_2; + v4 = Long.rotateLeft(v4, 31); + v4 *= PRIME64_1; + hash ^= v4; + hash = hash * PRIME64_1 + PRIME64_4; + } + else + { + hash = PRIME64_5; + } + + hash += length; + + while (remaining >= 8) + { + long k1 = input.getLong(off); + k1 *= PRIME64_2; + k1 = Long.rotateLeft(k1, 31); + k1 *= PRIME64_1; + hash ^= k1; + hash = Long.rotateLeft(hash, 27) * PRIME64_1 + PRIME64_4; + off += 8; + remaining -= 8; + } + + if (remaining >= 4) + { + hash ^= (input.getInt(off) & 0xFFFFFFFFL) * PRIME64_1; + hash = Long.rotateLeft(hash, 23) * PRIME64_2 + PRIME64_3; + off += 4; + remaining -= 4; + } + + while (remaining != 0) + { + hash ^= (input.get(off) & 0xFF) * PRIME64_5; + hash = Long.rotateLeft(hash, 11) * PRIME64_1; + --remaining; + ++off; + } + + return finalize(hash); + } + + private static long finalize(long hash) + { + hash ^= hash >>> 33; + hash *= PRIME64_2; + hash ^= hash >>> 29; + hash *= PRIME64_3; + hash ^= hash >>> 32; + return hash; + } +} diff --git a/webbeans-impl/src/main/java/org/apache/webbeans/proxy/AbstractProxyFactory.java b/webbeans-impl/src/main/java/org/apache/webbeans/proxy/AbstractProxyFactory.java index 2e90df4..34868df 100644 --- a/webbeans-impl/src/main/java/org/apache/webbeans/proxy/AbstractProxyFactory.java +++ b/webbeans-impl/src/main/java/org/apache/webbeans/proxy/AbstractProxyFactory.java @@ -18,6 +18,7 @@ */ package org.apache.webbeans.proxy; +import static java.util.stream.Collectors.joining; import static org.apache.xbean.asm8.ClassReader.SKIP_CODE; import static org.apache.xbean.asm8.ClassReader.SKIP_DEBUG; import static org.apache.xbean.asm8.ClassReader.SKIP_FRAMES; @@ -28,10 +29,12 @@ import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Arrays; +import java.util.stream.Stream; import org.apache.webbeans.config.WebBeansContext; import org.apache.webbeans.exception.ProxyGenerationException; import org.apache.webbeans.exception.WebBeansException; +import org.apache.webbeans.hash.XxHash64; import org.apache.webbeans.spi.DefiningClassService; import org.apache.xbean.asm8.ClassReader; import org.apache.xbean.asm8.ClassWriter; @@ -59,6 +62,7 @@ public abstract class AbstractProxyFactory private final DefiningClassService definingService; private final boolean useStaticNames; + private final boolean useXXhash64; protected WebBeansContext webBeansContext; @@ -80,6 +84,8 @@ public abstract class AbstractProxyFactory definingService = webBeansContext.getService(DefiningClassService.class); useStaticNames = Boolean.parseBoolean(webBeansContext.getOpenWebBeansConfiguration() .getProperty("org.apache.webbeans.proxy.useStaticNames")); + useXXhash64 = Boolean.parseBoolean(webBeansContext.getOpenWebBeansConfiguration() + .getProperty("org.apache.webbeans.proxy.staticNames.useXxHash64")); unsafe = definingService == null ? new Unsafe() : null; } @@ -190,7 +196,9 @@ public abstract class AbstractProxyFactory * Detect a free classname based on the given one * @param proxyClassName * @return + * @deprecated use {@link #getUnusedProxyClassName(ClassLoader, String, Method[], Method[])}. */ + @Deprecated protected String getUnusedProxyClassName(ClassLoader classLoader, String proxyClassName) { proxyClassName = fixPreservedPackages(proxyClassName); @@ -219,6 +227,34 @@ public abstract class AbstractProxyFactory throw new WebBeansException("Unable to detect a free proxy class name based on: " + proxyClassName); } + protected String getUnusedProxyClassName(ClassLoader classLoader, String proxyClassName, + Method[] proxiedMethods, Method[] notProxiedMethods) + { + proxyClassName = fixPreservedPackages(proxyClassName); + + + if (useStaticNames) + { + return proxyClassName + uniqueHash(proxiedMethods, notProxiedMethods); + } + return getUnusedProxyClassName(classLoader, proxyClassName); + } + + protected String uniqueHash(Method[] proxiedMethods, Method[] notProxiedMethods) + { + if (useXXhash64) + { + // xxhash64 has very low collision so for this kind of has it is safe enough + // and enables to avoid a big concatenation for names + return Long.toString(Math.abs(XxHash64.apply(Stream.concat( + Stream.of(proxiedMethods).map(Method::toGenericString).sorted(), + Stream.of(notProxiedMethods).map(Method::toGenericString).map(it -> "<NOT>" + it).sorted() + ).collect(joining("_"))))); + } + // else unsafe - 1 proxy per class max! + return "0"; + } + protected <T> String getSignedClassProxyName(final Class<T> classToProxy) { // avoid java.lang.SecurityException: class's signer information 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 37ad044..55b0687 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 @@ -23,7 +23,6 @@ import org.apache.webbeans.config.WebBeansContext; import org.apache.webbeans.exception.ProxyGenerationException; import org.apache.webbeans.exception.WebBeansConfigurationException; import org.apache.webbeans.intercept.InterceptorResolutionService; -import org.apache.webbeans.logger.WebBeansLoggerFacade; import org.apache.webbeans.util.Asserts; import org.apache.webbeans.util.ExceptionUtil; import org.apache.xbean.asm8.ClassWriter; @@ -42,7 +41,6 @@ import java.lang.reflect.Modifier; import java.util.Collection; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; -import java.util.logging.Logger; /** * Generate a dynamic subclass which has exactly 1 delegation point instance @@ -55,9 +53,6 @@ import java.util.logging.Logger; */ public class InterceptorDecoratorProxyFactory extends AbstractProxyFactory { - private static final Logger logger = WebBeansLoggerFacade.getLogger(InterceptorDecoratorProxyFactory.class); - - /** the name of the field which stores the proxied instance */ public static final String FIELD_PROXIED_INSTANCE = "owbIntDecProxiedInstance"; @@ -206,7 +201,8 @@ public class InterceptorDecoratorProxyFactory extends AbstractProxyFactory { String proxyClassName = getUnusedProxyClassName( classLoader, - (classToProxy.getSigners() != null ? getSignedClassProxyName(classToProxy) : classToProxy.getName()) + "$$OwbInterceptProxy"); + (classToProxy.getSigners() != null ? getSignedClassProxyName(classToProxy) : classToProxy.getName()) + "$$OwbInterceptProxy", + interceptedMethods, nonInterceptedMethods); Class<T> clazz = createProxyClass(classLoader, proxyClassName, classToProxy, interceptedMethods, nonInterceptedMethods); diff --git a/webbeans-impl/src/main/java/org/apache/webbeans/proxy/NormalScopeProxyFactory.java b/webbeans-impl/src/main/java/org/apache/webbeans/proxy/NormalScopeProxyFactory.java index 1070370..8d5a57e 100644 --- a/webbeans-impl/src/main/java/org/apache/webbeans/proxy/NormalScopeProxyFactory.java +++ b/webbeans-impl/src/main/java/org/apache/webbeans/proxy/NormalScopeProxyFactory.java @@ -232,10 +232,6 @@ public class NormalScopeProxyFactory extends AbstractProxyFactory public <T> Class<T> createProxyClass(ClassLoader classLoader, Class<T> classToProxy) throws ProxyGenerationException { - String proxyClassName = getUnusedProxyClassName( - classLoader, - (classToProxy.getSigners() != null ? getSignedClassProxyName(classToProxy) : classToProxy.getName()) + "$$OwbNormalScopeProxy"); - Method[] nonInterceptedMethods; Method[] interceptedMethods = null; if (classToProxy.isInterface()) @@ -268,6 +264,11 @@ public class NormalScopeProxyFactory extends AbstractProxyFactory interceptedMethods = protectedMethods.toArray(new Method[protectedMethods.size()]); } + String proxyClassName = getUnusedProxyClassName( + classLoader, + (classToProxy.getSigners() != null ? getSignedClassProxyName(classToProxy) : classToProxy.getName()) + "$$OwbNormalScopeProxy", + interceptedMethods, nonInterceptedMethods); + Class<T> clazz = createProxyClass(classLoader, proxyClassName, classToProxy, interceptedMethods, nonInterceptedMethods); if (interceptedMethods != null && interceptedMethods.length > 0) diff --git a/webbeans-impl/src/test/java/org/apache/webbeans/hash/XxHash64Test.java b/webbeans-impl/src/test/java/org/apache/webbeans/hash/XxHash64Test.java new file mode 100644 index 0000000..53e490a --- /dev/null +++ b/webbeans-impl/src/test/java/org/apache/webbeans/hash/XxHash64Test.java @@ -0,0 +1,32 @@ +/* + * 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.hash; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class XxHash64Test +{ + @Test + public void sanityCheck() + { + assertEquals(3728699739546630719L, XxHash64.apply("foo")); + } +} diff --git a/webbeans-impl/src/test/java/org/apache/webbeans/test/managed/ProxyFactoryTest.java b/webbeans-impl/src/test/java/org/apache/webbeans/test/managed/ProxyFactoryTest.java index 1c70807..38f3945 100644 --- a/webbeans-impl/src/test/java/org/apache/webbeans/test/managed/ProxyFactoryTest.java +++ b/webbeans-impl/src/test/java/org/apache/webbeans/test/managed/ProxyFactoryTest.java @@ -19,6 +19,11 @@ package org.apache.webbeans.test.managed; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; import java.util.ArrayList; import java.util.Collection; import java.util.Set; @@ -26,9 +31,22 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; +import javax.enterprise.context.ApplicationScoped; import javax.enterprise.context.spi.CreationalContext; +import javax.enterprise.inject.Produces; +import javax.enterprise.inject.Typed; +import javax.enterprise.inject.literal.NamedLiteral; import javax.enterprise.inject.spi.Bean; - +import javax.enterprise.inject.spi.Extension; +import javax.enterprise.inject.spi.InterceptionFactory; +import javax.enterprise.util.AnnotationLiteral; +import javax.inject.Named; +import javax.inject.Qualifier; +import javax.interceptor.Interceptor; +import javax.interceptor.InterceptorBinding; +import javax.interceptor.InvocationContext; + +import org.apache.webbeans.test.discovery.InterceptorAnnotatedDiscoveryTest; import org.junit.Assert; import org.apache.webbeans.config.WebBeansContext; @@ -37,8 +55,80 @@ import org.apache.webbeans.test.AbstractUnitTest; import org.apache.webbeans.test.managed.multipleinterfaces.MyEntityServiceImpl; import org.junit.Test; +import static org.junit.Assert.assertEquals; + public class ProxyFactoryTest extends AbstractUnitTest { + @InterceptorBinding + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.METHOD, ElementType.TYPE}) + public @interface Foo + { + } + + @Interceptor + @Foo + public static class FooInterceptor + { + public Object foo(InvocationContext ctx) throws Exception + { + return ctx.proceed(); + } + } + + public static class ABean + { + public String foo() + { + return "foo"; + } + + public String bar() + { + return "bar"; + } + } + + @ApplicationScoped + public static class ABeanFactories + { + @Produces + @Named("bean1") + @ApplicationScoped + public ABean bean1() + { + return new ABean(); + } + + @Foo + @Named("bean2") + @Produces + public ABean bean2(final InterceptionFactory<ABean> factory) + { + factory.configure() + .methods() + .iterator().next().add(new AnnotationLiteral<Foo>() + { + }); + return factory.createInterceptedInstance(new ABean()); + } + } + + @Test + public void stableNameMultipleTypes() + { + addConfiguration("org.apache.webbeans.proxy.useStaticNames", "true"); + addConfiguration("org.apache.webbeans.proxy.staticNames.useXxHash64", "true"); + startContainer(ABeanFactories.class, FooInterceptor.class); + final ABean bean1 = getInstance("bean1"); + final ABean bean2 = getInstance("bean2"); + assertEquals( + "org.apache.webbeans.test.managed.ProxyFactoryTest$ABean$$OwbNormalScopeProxy8050522010792129812", + bean1.getClass().getName()); + assertEquals( + "org.apache.webbeans.test.managed.ProxyFactoryTest$ABean$$OwbInterceptProxy5751833139562769786", + bean2.getClass().getName()); + } @SuppressWarnings("unchecked") @Test