Expand class customization capabilities 11/81711/5
authorRobert Varga <robert.varga@pantheon.tech>
Tue, 23 Apr 2019 08:27:51 +0000 (10:27 +0200)
committerRobert Varga <robert.varga@pantheon.tech>
Wed, 24 Apr 2019 08:14:54 +0000 (10:14 +0200)
This expands CodecClassLoader.Customizer with the ability to
cross-reference generated classes, so a generated class can
depend on another generated class -- which can potentially live
in a different classloader. This capability is required to support
stream writers, as they delegate to each other.

JIRA: MDSAL-401
JIRA: MDSAL-443
Change-Id: I446dcfe10c742c670f2021123acab600a28c2514
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/OpaqueNodeCodecContext.java
binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/loader/CodecClassLoader.java
binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/loader/LeafCodecClassLoader.java
binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/loader/RootCodecClassLoader.java
binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/loader/package-info.java

index a64145e11c17ad41acd865bcb2e9d2990778ef8e..dbad1573d54a9ca83739eda9a5531c15fc211f6c 100644 (file)
@@ -12,6 +12,7 @@ import static com.google.common.base.Verify.verify;
 import static java.util.Objects.requireNonNull;
 
 import com.google.common.base.Throwables;
+import com.google.common.collect.ImmutableList;
 import java.io.IOException;
 import java.lang.invoke.MethodHandle;
 import java.lang.invoke.MethodHandles;
@@ -137,6 +138,7 @@ abstract class OpaqueNodeCodecContext<T extends OpaqueObject<T>> extends ValueNo
                 (pool, binding, generated) -> {
                     generated.addInterface(binding);
                     generated.setModifiers(Modifier.PUBLIC | Modifier.FINAL);
+                    return ImmutableList.of();
                 });
         } catch (CannotCompileException | IOException | NotFoundException e) {
             throw new LinkageError("Failed to instantiate prototype for " + bindingClass, e);
index 22f7396957fbf565ac2eebe4e978eba0ede8ebed..c8b2a60140ab512569a57bb2f5a4d05853414508 100644 (file)
@@ -10,10 +10,15 @@ package org.opendaylight.mdsal.binding.dom.codec.loader;
 import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.base.Verify.verify;
 import static com.google.common.base.Verify.verifyNotNull;
+import static java.util.Objects.requireNonNull;
 
 import com.google.common.annotations.Beta;
 import com.google.common.base.Strings;
+import com.google.common.base.Supplier;
 import java.io.IOException;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
 import javassist.CannotCompileException;
 import javassist.ClassPool;
 import javassist.CtClass;
@@ -21,6 +26,8 @@ import javassist.LoaderClassPath;
 import javassist.NotFoundException;
 import org.eclipse.jdt.annotation.NonNull;
 import org.opendaylight.mdsal.binding.spec.reflect.BindingReflections;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * A ClassLoader hosting types generated for a particular type. A root instance is attached to a
@@ -56,18 +63,32 @@ public abstract class CodecClassLoader extends ClassLoader {
          * @param loader CodecClassLoader which will hold the class. It can be used to lookup/instantiate other classes
          * @param bindingClass Binding class for which the customized class is being generated
          * @param generated The class being generated
+         * @return A set of generated classes the generated class depends on
          * @throws CannotCompileException if the customizer cannot perform partial compilation
          * @throws NotFoundException if the customizer cannot find a required class
          * @throws IOException if the customizer cannot perform partial loading
          */
-        void customize(@NonNull CodecClassLoader loader, @NonNull CtClass bindingClass, @NonNull CtClass generated)
-                throws CannotCompileException, NotFoundException, IOException;
+        @NonNull List<Class<?>> customize(@NonNull CodecClassLoader loader, @NonNull CtClass bindingClass,
+                @NonNull CtClass generated) throws CannotCompileException, NotFoundException, IOException;
+
+        /**
+         * Run the specified loader in a customized environment. The environment customizations must be cleaned up by
+         * the time this method returns. The default implementation performs no customization.
+         *
+         * @param loader Class loader to execute
+         * @return Class returned by the loader
+         */
+        default Class<?> customizeLoading(final @NonNull Supplier<Class<?>> loader) {
+            return loader.get();
+        }
     }
 
     static {
         verify(ClassLoader.registerAsParallelCapable());
     }
 
+    private static final Logger LOG = LoggerFactory.getLogger(CodecClassLoader.class);
+
     private final ClassPool classPool;
 
     private CodecClassLoader(final ClassLoader parentLoader, final ClassPool parentPool) {
@@ -116,7 +137,8 @@ public abstract class CodecClassLoader extends ClassLoader {
     public final Class<?> generateSubclass(final CtClass superClass, final Class<?> bindingInterface,
             final String suffix, final Customizer customizer) throws CannotCompileException, IOException,
                 NotFoundException {
-        return findClassLoader(bindingInterface).doGenerateSubclass(superClass, bindingInterface, suffix, customizer);
+        return findClassLoader(requireNonNull(bindingInterface))
+                .doGenerateSubclass(superClass, bindingInterface, suffix, customizer);
     }
 
     final @NonNull CtClass getLocalFrozen(final String name) throws NotFoundException {
@@ -127,7 +149,24 @@ public abstract class CodecClassLoader extends ClassLoader {
         }
     }
 
-    abstract @NonNull CodecClassLoader findClassLoader(Class<?> bindingClass);
+    /**
+     * Append specified loaders to this class loader for the purposes of looking up generated classes. Note that the
+     * loaders are expected to have required classes already loaded. This is required to support generation of
+     * inter-dependent structures, such as those used for streaming binding interfaces.
+     *
+     * @param newLoaders Loaders to append
+     * @throws NullPointerException if {@code loaders} is null
+     */
+    abstract void appendLoaders(@NonNull Set<LeafCodecClassLoader> newLoaders);
+
+    /**
+     * Find the loader responsible for holding classes related to a binding class.
+     *
+     * @param bindingClass Class to locate
+     * @return a Loader instance
+     * @throws NullPointerException if {@code bindingClass} is null
+     */
+    abstract @NonNull CodecClassLoader findClassLoader(@NonNull Class<?> bindingClass);
 
     private Class<?> doGenerateSubclass(final CtClass superClass, final Class<?> bindingInterface, final String suffix,
             final Customizer customizer) throws CannotCompileException, IOException, NotFoundException {
@@ -150,18 +189,22 @@ public abstract class CodecClassLoader extends ClassLoader {
                 final byte[] byteCode;
                 final CtClass generated = verifyNotNull(classPool.makeClass(fqn, superClass));
                 try {
-                    customizer.customize(this, bindingCt, generated);
+                    final List<Class<?>> deps = customizer.customize(this, bindingCt, generated);
                     final String ctName = generated.getName();
                     verify(fqn.equals(ctName), "Target class is %s returned result is %s", fqn, ctName);
+                    processDependencies(deps);
+
                     byteCode = generated.toBytecode();
                 } finally {
                     // Always detach the result, as we will never use it again
                     generated.detach();
                 }
 
-                final Class<?> newClass = defineClass(fqn, byteCode, 0, byteCode.length);
-                resolveClass(newClass);
-                return newClass;
+                return customizer.customizeLoading(() -> {
+                    final Class<?> newClass = defineClass(fqn, byteCode, 0, byteCode.length);
+                    resolveClass(newClass);
+                    return newClass;
+                });
             } finally {
                 // Binding interfaces are used only a few times, hence it does not make sense to cache them in the class
                 // pool.
@@ -170,4 +213,29 @@ public abstract class CodecClassLoader extends ClassLoader {
             }
         }
     }
+
+    private void processDependencies(final List<Class<?>> deps) {
+        final Set<LeafCodecClassLoader> depLoaders = new HashSet<>();
+        for (Class<?> dep : deps) {
+            final ClassLoader depLoader = dep.getClassLoader();
+            verify(depLoader instanceof CodecClassLoader, "Dependency %s is not a generated class", dep);
+            if (this.equals(depLoader)) {
+                // Same loader, skip
+                continue;
+            }
+
+            try {
+                loadClass(dep.getName());
+            } catch (ClassNotFoundException e) {
+                LOG.debug("Cannot find {} in local loader, attempting to compensate", dep, e);
+                // Root loader is always visible from a leaf, hence the dependency can only be a leaf
+                verify(depLoader instanceof LeafCodecClassLoader, "Dependency loader %s is not a leaf", depLoader);
+                depLoaders.add((LeafCodecClassLoader) depLoader);
+            }
+        }
+
+        if (!depLoaders.isEmpty()) {
+            appendLoaders(depLoaders);
+        }
+    }
 }
index fdf29037614afb3e8c30281b34a1f2eb55da2663..eebeb6c8184474738f618b89e29b7ba23618a7b2 100644 (file)
@@ -10,7 +10,14 @@ package org.opendaylight.mdsal.binding.dom.codec.loader;
 import static com.google.common.base.Verify.verify;
 import static java.util.Objects.requireNonNull;
 
+import com.google.common.collect.ImmutableSet;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
 import org.eclipse.jdt.annotation.NonNull;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 // A leaf class loader, binding together a root class loader and some other class loader
 final class LeafCodecClassLoader extends CodecClassLoader {
@@ -18,8 +25,15 @@ final class LeafCodecClassLoader extends CodecClassLoader {
         verify(registerAsParallelCapable());
     }
 
-    private final @NonNull ClassLoader target;
+    private static final Logger LOG = LoggerFactory.getLogger(LeafCodecClassLoader.class);
+
     private final @NonNull RootCodecClassLoader root;
+    private final @NonNull ClassLoader target;
+
+    @SuppressWarnings("rawtypes")
+    private static final AtomicReferenceFieldUpdater<LeafCodecClassLoader, ImmutableSet> DEPENDENCIES_UPDATER =
+            AtomicReferenceFieldUpdater.newUpdater(LeafCodecClassLoader.class, ImmutableSet.class, "dependencies");
+    private volatile ImmutableSet<LeafCodecClassLoader> dependencies = ImmutableSet.of();
 
     LeafCodecClassLoader(final RootCodecClassLoader root, final ClassLoader target) {
         super(root);
@@ -29,7 +43,20 @@ final class LeafCodecClassLoader extends CodecClassLoader {
 
     @Override
     protected Class<?> findClass(final String name) throws ClassNotFoundException {
-        return target.loadClass(name);
+        try {
+            return target.loadClass(name);
+        } catch (ClassNotFoundException e) {
+            LOG.trace("Class {} not found in target, looking through dependencies", name);
+            for (LeafCodecClassLoader loader : dependencies) {
+                final Class<?> loaded = loader.findLoadedClass(name);
+                if (loaded != null) {
+                    LOG.trace("Class {} found in dependency {}", name, loader);
+                    return loaded;
+                }
+            }
+
+            throw e;
+        }
     }
 
     @Override
@@ -37,4 +64,19 @@ final class LeafCodecClassLoader extends CodecClassLoader {
         final ClassLoader bindingTarget = bindingClass.getClassLoader();
         return target.equals(bindingTarget) ? this : root.findClassLoader(bindingClass);
     }
+
+    @Override
+    void appendLoaders(final Set<LeafCodecClassLoader> newLoaders) {
+        while (true) {
+            final ImmutableSet<LeafCodecClassLoader> local = dependencies;
+            final List<LeafCodecClassLoader> builder = new ArrayList<>(local.size() + newLoaders.size());
+            builder.addAll(local);
+            builder.addAll(newLoaders);
+            final ImmutableSet<LeafCodecClassLoader> updated = ImmutableSet.copyOf(builder);
+            if (local.equals(updated) || DEPENDENCIES_UPDATER.compareAndSet(this, local, updated)) {
+                // No need for an update or the update was successful
+                return;
+            }
+        }
+    }
 }
index 225941dcfdeafc6fe3bde7b3bcabf1089913da13..1b2b8023d9e198307699767a329f63c85f3247f0 100644 (file)
@@ -13,6 +13,7 @@ import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableMap.Builder;
 import java.security.AccessController;
 import java.security.PrivilegedAction;
+import java.util.Set;
 import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -80,6 +81,14 @@ final class RootCodecClassLoader extends CodecClassLoader {
         }
     }
 
+    @Override
+    void appendLoaders(final Set<LeafCodecClassLoader> newLoaders) {
+        // Root loader should never see the requirement for other loaders, as that would violate loop-free nature
+        // of generated code: if a binding class is hosted in root loader, all its references must be visible from
+        // the root loader and hence all the generated code ends up residing in the root loader, too.
+        throw new IllegalStateException("Attempted to extend root loader with " + newLoaders);
+    }
+
     private boolean isOurClass(final Class<?> bindingClass) {
         final Class<?> ourClass;
         try {
index d66ec127e9e0c77fefbfe27b364d0b50cd69be6b..4b0a42d36c3414d06f018f9af61a1ac8ca3b3331 100644 (file)
@@ -16,5 +16,9 @@
  *     serves as the ClassLoader holding runtime-generated codecs.
  * </li>
  * </ul>
+ *
+ * <p>
+ * While the interfaces and classes in this package may be publicly accessible, they are an implementation detail and
+ * may change incompatibly at any time.
  */
 package org.opendaylight.mdsal.binding.dom.codec.loader;
\ No newline at end of file