From 3106784dac024576f56095b2aff7ef715538da08 Mon Sep 17 00:00:00 2001 From: Robert Varga Date: Tue, 23 Apr 2019 10:27:51 +0200 Subject: [PATCH] Expand class customization capabilities 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 --- .../codec/impl/OpaqueNodeCodecContext.java | 2 + .../dom/codec/loader/CodecClassLoader.java | 84 +++++++++++++++++-- .../codec/loader/LeafCodecClassLoader.java | 46 +++++++++- .../codec/loader/RootCodecClassLoader.java | 9 ++ .../dom/codec/loader/package-info.java | 4 + 5 files changed, 135 insertions(+), 10 deletions(-) diff --git a/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/OpaqueNodeCodecContext.java b/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/OpaqueNodeCodecContext.java index a64145e11c..dbad1573d5 100644 --- a/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/OpaqueNodeCodecContext.java +++ b/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/OpaqueNodeCodecContext.java @@ -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> 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); diff --git a/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/loader/CodecClassLoader.java b/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/loader/CodecClassLoader.java index 22f7396957..c8b2a60140 100644 --- a/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/loader/CodecClassLoader.java +++ b/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/loader/CodecClassLoader.java @@ -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> 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> 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 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> 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> deps) { + final Set 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); + } + } } diff --git a/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/loader/LeafCodecClassLoader.java b/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/loader/LeafCodecClassLoader.java index fdf2903761..eebeb6c818 100644 --- a/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/loader/LeafCodecClassLoader.java +++ b/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/loader/LeafCodecClassLoader.java @@ -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 DEPENDENCIES_UPDATER = + AtomicReferenceFieldUpdater.newUpdater(LeafCodecClassLoader.class, ImmutableSet.class, "dependencies"); + private volatile ImmutableSet 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 newLoaders) { + while (true) { + final ImmutableSet local = dependencies; + final List builder = new ArrayList<>(local.size() + newLoaders.size()); + builder.addAll(local); + builder.addAll(newLoaders); + final ImmutableSet 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; + } + } + } } diff --git a/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/loader/RootCodecClassLoader.java b/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/loader/RootCodecClassLoader.java index 225941dcfd..1b2b8023d9 100644 --- a/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/loader/RootCodecClassLoader.java +++ b/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/loader/RootCodecClassLoader.java @@ -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 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 { diff --git a/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/loader/package-info.java b/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/loader/package-info.java index d66ec127e9..4b0a42d36c 100644 --- a/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/loader/package-info.java +++ b/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/loader/package-info.java @@ -16,5 +16,9 @@ * serves as the ClassLoader holding runtime-generated codecs. * * + * + *

+ * 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 -- 2.36.6