You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@openwebbeans.apache.org by rm...@apache.org on 2020/06/07 14:31:14 UTC

[openwebbeans] branch master updated: [OWB-1325] better impl of stable names for proxies

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 <rm...@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