X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=blobdiff_plain;f=binding%2Fmdsal-binding-dom-codec%2Fsrc%2Fmain%2Fjava%2Forg%2Fopendaylight%2Fmdsal%2Fbinding%2Fdom%2Fcodec%2Fimpl%2FDataObjectCodecContext.java;h=6505b3afdb0a3a47deb017f4707e0928165c85c3;hb=f6f3556fd543e3af5a239f5e8666c6305a6b8f5a;hp=3b615f25b0c199811ad013ba5de89e50b19e3487;hpb=856a7b36138eca857680928cbe50b47057888493;p=mdsal.git diff --git a/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/DataObjectCodecContext.java b/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/DataObjectCodecContext.java index 3b615f25b0..6505b3afdb 100644 --- a/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/DataObjectCodecContext.java +++ b/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/DataObjectCodecContext.java @@ -8,38 +8,41 @@ package org.opendaylight.mdsal.binding.dom.codec.impl; 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.Throwables; import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSortedMap; +import com.google.common.collect.ImmutableMap.Builder; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; -import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; -import java.lang.reflect.Proxy; -import java.util.Collection; +import java.util.Arrays; import java.util.Comparator; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Optional; -import java.util.SortedMap; -import java.util.TreeMap; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; import org.opendaylight.mdsal.binding.generator.api.ClassLoadingStrategy; +import org.opendaylight.mdsal.binding.model.api.JavaTypeName; import org.opendaylight.mdsal.binding.model.api.Type; import org.opendaylight.mdsal.binding.spec.reflect.BindingReflections; +import org.opendaylight.yangtools.concepts.Immutable; +import org.opendaylight.yangtools.util.ClassLoaderUtils; import org.opendaylight.yangtools.yang.binding.Augmentable; import org.opendaylight.yangtools.yang.binding.Augmentation; -import org.opendaylight.yangtools.yang.binding.AugmentationHolder; import org.opendaylight.yangtools.yang.binding.DataObject; import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; import org.opendaylight.yangtools.yang.binding.InstanceIdentifier.Item; +import org.opendaylight.yangtools.yang.binding.OpaqueObject; import org.opendaylight.yangtools.yang.common.QName; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier; @@ -57,46 +60,87 @@ import org.opendaylight.yangtools.yang.model.util.SchemaNodeUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -abstract class DataObjectCodecContext +/** + * This class is an implementation detail. It is public only due to technical reasons and may change at any time. + */ +@Beta +public abstract class DataObjectCodecContext extends DataContainerCodecContext { + private static final class Augmentations implements Immutable { + final ImmutableMap> byYang; + final ImmutableMap, DataContainerCodecPrototype> byStream; + + Augmentations(final ImmutableMap> byYang, + final ImmutableMap, DataContainerCodecPrototype> byStream) { + this.byYang = requireNonNull(byYang); + this.byStream = requireNonNull(byStream); + } + } + private static final Logger LOG = LoggerFactory.getLogger(DataObjectCodecContext.class); - private static final MethodType CONSTRUCTOR_TYPE = MethodType.methodType(void.class, InvocationHandler.class); - private static final MethodType DATAOBJECT_TYPE = MethodType.methodType(DataObject.class, InvocationHandler.class); + private static final MethodType CONSTRUCTOR_TYPE = MethodType.methodType(void.class, NormalizedNodeContainer.class); + private static final MethodType AUGMENTABLE_CONSTRUCTOR_TYPE = MethodType.methodType(void.class, + DataObjectCodecContext.class, NormalizedNodeContainer.class); + private static final MethodType DATAOBJECT_TYPE = MethodType.methodType(DataObject.class, + NormalizedNodeContainer.class); + private static final MethodType AUGMENTABLE_DATAOBJECT_TYPE = MethodType.methodType(DataObject.class, + DataObjectCodecContext.class, NormalizedNodeContainer.class); private static final Comparator METHOD_BY_ALPHABET = Comparator.comparing(Method::getName); + private static final Augmentations EMPTY_AUGMENTATIONS = new Augmentations(ImmutableMap.of(), ImmutableMap.of()); - private final ImmutableMap> leafChild; + private final ImmutableMap leafChild; private final ImmutableMap byYang; - private final ImmutableSortedMap byMethod; private final ImmutableMap, DataContainerCodecPrototype> byStreamClass; private final ImmutableMap, DataContainerCodecPrototype> byBindingArgClass; private final ImmutableMap possibleAugmentations; private final MethodHandle proxyConstructor; - private final ConcurrentMap> byYangAugmented = - new ConcurrentHashMap<>(); - private final ConcurrentMap, DataContainerCodecPrototype> byStreamAugmented = new ConcurrentHashMap<>(); + // FIXME: the presence of these two volatile fields may be preventing us from being able to improve + // DataContainerCodecPrototype.get() publication. + @SuppressWarnings("rawtypes") + private static final AtomicReferenceFieldUpdater + AUGMENTATIONS_UPDATER = AtomicReferenceFieldUpdater.newUpdater(DataObjectCodecContext.class, + Augmentations.class, "augmentations"); + private volatile Augmentations augmentations = EMPTY_AUGMENTATIONS; + + private volatile ImmutableMap, DataContainerCodecPrototype> mismatchedAugmented = ImmutableMap.of(); DataObjectCodecContext(final DataContainerCodecPrototype prototype) { + this(prototype, null); + } + + DataObjectCodecContext(final DataContainerCodecPrototype prototype, + final Entry keyMethod) { super(prototype); - this.leafChild = factory().getLeafNodes(getBindingClass(), getSchema()); + final Class bindingClass = getBindingClass(); + this.leafChild = factory().getLeafNodes(bindingClass, getSchema()); - final Map, Method> clsToMethod = BindingReflections.getChildrenClassToMethod(getBindingClass()); + final Map, Method> clsToMethod = BindingReflections.getChildrenClassToMethod(bindingClass); final Map byYangBuilder = new HashMap<>(); - final SortedMap byMethodBuilder = new TreeMap<>(METHOD_BY_ALPHABET); + final Map tmpMethodToSupplier = new HashMap<>(); final Map, DataContainerCodecPrototype> byStreamClassBuilder = new HashMap<>(); final Map, DataContainerCodecPrototype> byBindingArgClassBuilder = new HashMap<>(); // Adds leaves to mapping - for (final LeafNodeCodecContext leaf : leafChild.values()) { - byMethodBuilder.put(leaf.getGetter(), leaf); + for (final ValueNodeCodecContext leaf : leafChild.values()) { + tmpMethodToSupplier.put(leaf.getGetter(), leaf); byYangBuilder.put(leaf.getDomPathArgument(), leaf); } for (final Entry, Method> childDataObj : clsToMethod.entrySet()) { - final DataContainerCodecPrototype childProto = loadChildPrototype(childDataObj.getKey()); - byMethodBuilder.put(childDataObj.getValue(), childProto); + final Method method = childDataObj.getValue(); + verify(!method.isDefault(), "Unexpected default method %s in %s", method, bindingClass); + + final Class retClass = childDataObj.getKey(); + if (OpaqueObject.class.isAssignableFrom(retClass)) { + // Filter OpaqueObjects, they are not containers + continue; + } + + final DataContainerCodecPrototype childProto = loadChildPrototype(retClass); + tmpMethodToSupplier.put(method, childProto); byStreamClassBuilder.put(childProto.getBindingClass(), childProto); byYangBuilder.put(childProto.getYangArg(), childProto); if (childProto.isChoice()) { @@ -106,36 +150,124 @@ abstract class DataObjectCodecContext propBuilder = ImmutableMap.builderWithExpectedSize( + properties.length); + for (Method prop : properties) { + propBuilder.put(prop, verifyNotNull(tmpMethodToSupplier.get(prop))); + } + this.byYang = ImmutableMap.copyOf(byYangBuilder); this.byStreamClass = ImmutableMap.copyOf(byStreamClassBuilder); byBindingArgClassBuilder.putAll(byStreamClass); this.byBindingArgClass = ImmutableMap.copyOf(byBindingArgClassBuilder); - - if (Augmentable.class.isAssignableFrom(getBindingClass())) { + final Class> generatedClass; + final MethodType ctorType; + if (Augmentable.class.isAssignableFrom(bindingClass)) { this.possibleAugmentations = factory().getRuntimeContext().getAvailableAugmentationTypes(getSchema()); + generatedClass = CodecDataObjectGenerator.generateAugmentable(prototype.getFactory().getLoader(), + bindingClass, propBuilder.build(), keyMethod); + ctorType = AUGMENTABLE_CONSTRUCTOR_TYPE; } else { this.possibleAugmentations = ImmutableMap.of(); + generatedClass = CodecDataObjectGenerator.generate(prototype.getFactory().getLoader(), bindingClass, + propBuilder.build(), keyMethod); + ctorType = CONSTRUCTOR_TYPE; } reloadAllAugmentations(); - final Class proxyClass = Proxy.getProxyClass(getBindingClass().getClassLoader(), getBindingClass(), - AugmentationHolder.class); + final MethodHandle ctor; try { - proxyConstructor = MethodHandles.publicLookup().findConstructor(proxyClass, CONSTRUCTOR_TYPE) - .asType(DATAOBJECT_TYPE); + ctor = MethodHandles.publicLookup().findConstructor(generatedClass, ctorType); } catch (NoSuchMethodException | IllegalAccessException e) { - throw new IllegalStateException("Failed to find contructor for class " + proxyClass, e); + throw new LinkageError("Failed to find contructor for class " + generatedClass, e); + } + + if (Augmentable.class.isAssignableFrom(bindingClass)) { + proxyConstructor = ctor.asType(AUGMENTABLE_DATAOBJECT_TYPE).bindTo(this); + } else { + proxyConstructor = ctor.asType(DATAOBJECT_TYPE); } } + // This method could be synchronized, but that would mean that concurrent attempts to load an invalid augmentation + // would end up being unnecessarily contended -- blocking real progress and not being able to run concurrently + // while producing no effect. We therefore use optimistic read + CAS. private void reloadAllAugmentations() { - for (final Entry augment : possibleAugmentations.entrySet()) { - final DataContainerCodecPrototype augProto = getAugmentationPrototype(augment.getValue()); + // Load current values + Augmentations oldAugmentations = augmentations; + + // FIXME: can we detect when we have both maps fully populated and skip all of this? + + // Scratch space for additions + final Map> addByYang = new HashMap<>(); + final Map, DataContainerCodecPrototype> addByStream = new HashMap<>(); + + // Iterate over all possibilities, checking for modifications. + for (final Type augment : possibleAugmentations.values()) { + final DataContainerCodecPrototype augProto = getAugmentationPrototype(augment); if (augProto != null) { - byYangAugmented.putIfAbsent(augProto.getYangArg(), augProto); - byStreamAugmented.putIfAbsent(augProto.getBindingClass(), augProto); + final PathArgument yangArg = augProto.getYangArg(); + final Class bindingClass = augProto.getBindingClass(); + if (!oldAugmentations.byYang.containsKey(yangArg)) { + if (addByYang.putIfAbsent(yangArg, augProto) == null) { + LOG.trace("Discovered new YANG mapping {} -> {} in {}", yangArg, augProto, this); + } + } + if (!oldAugmentations.byStream.containsKey(bindingClass)) { + if (addByStream.putIfAbsent(bindingClass, augProto) == null) { + LOG.trace("Discovered new class mapping {} -> {} in {}", bindingClass, augProto, this); + } + } + } + } + + while (true) { + if (addByYang.isEmpty() && addByStream.isEmpty()) { + LOG.trace("No new augmentations discovered in {}", this); + return; + } + + // We have some additions, propagate them out + final Augmentations newAugmentations = new Augmentations(concatMaps(oldAugmentations.byYang, addByYang), + concatMaps(oldAugmentations.byStream, addByStream)); + if (AUGMENTATIONS_UPDATER.compareAndSet(this, oldAugmentations, newAugmentations)) { + // Success, we are done + return; + } + + // We have raced installing new augmentations, read them again, remove everything present in the installed + // once and try again. This may mean that we end up not doing anything, but that's fine. + oldAugmentations = augmentations; + + // We could use Map.removeAll(oldAugmentations.byYang.keySet()), but that forces the augmentation's keyset + // to be materialized, which we otherwise do not need. Hence we do this the other way around, instantiating + // our temporary maps' keySets and iterating over them. That's fine as we'll be throwing those maps away. + removeMapKeys(addByYang, oldAugmentations.byYang); + removeMapKeys(addByStream, oldAugmentations.byStream); + } + } + + private static ImmutableMap concatMaps(final ImmutableMap old, final Map add) { + if (add.isEmpty()) { + return old; + } + + final Builder builder = ImmutableMap.builderWithExpectedSize(old.size() + add.size()); + builder.putAll(old); + builder.putAll(add); + return builder.build(); + } + + private static void removeMapKeys(final Map removeFrom, final ImmutableMap map) { + final Iterator it = removeFrom.keySet().iterator(); + while (it.hasNext()) { + if (map.containsKey(it.next())) { + it.remove(); } } } @@ -179,13 +311,13 @@ abstract class DataObjectCodecContext context = - childNonNull(ctxProto, argType, "Class %s is not valid child of %s", argType, getBindingClass()).get(); + final DataContainerCodecContext context = childNonNull(ctxProto, argType, + "Class %s is not valid child of %s", argType, getBindingClass()).get(); if (context instanceof ChoiceNodeCodecContext) { final ChoiceNodeCodecContext choice = (ChoiceNodeCodecContext) context; choice.addYangPathArgument(arg, builder); - final java.util.Optional> caseType = arg.getCaseType(); + final Optional> caseType = arg.getCaseType(); final Class type = arg.getType(); final DataContainerCodecContext caze; if (caseType.isPresent()) { @@ -202,9 +334,8 @@ abstract class DataObjectCodecContext yangPathArgumentChild(final YangInstanceIdentifier.PathArgument arg) { + public NodeCodecContext yangPathArgumentChild(final YangInstanceIdentifier.PathArgument arg) { final NodeContextSupplier childSupplier; if (arg instanceof NodeIdentifierWithPredicates) { childSupplier = byYang.get(new NodeIdentifier(arg.getNodeType())); @@ -214,12 +345,11 @@ abstract class DataObjectCodecContext) childNonNull(childSupplier, arg, - "Argument %s is not valid child of %s", arg, getSchema()).get(); + return childNonNull(childSupplier, arg, "Argument %s is not valid child of %s", arg, getSchema()).get(); } - protected final LeafNodeCodecContext getLeafChild(final String name) { - final LeafNodeCodecContext value = leafChild.get(name); + protected final ValueNodeCodecContext getLeafChild(final String name) { + final ValueNodeCodecContext value = leafChild.get(name); return IncorrectNestingException.checkNonNull(value, "Leaf %s is not valid for %s", name, getBindingClass()); } @@ -269,53 +399,106 @@ abstract class DataObjectCodecContext yangAugmentationChild(final AugmentationIdentifier arg) { - final DataContainerCodecPrototype firstTry = byYangAugmented.get(arg); + final DataContainerCodecPrototype firstTry = augmentations.byYang.get(arg); if (firstTry != null) { return firstTry; } if (possibleAugmentations.containsKey(arg)) { + // Try to load augmentations, which will potentially update knownAugmentations, hence we re-load that field + // again. reloadAllAugmentations(); - return byYangAugmented.get(arg); + return augmentations.byYang.get(arg); } return null; } private @Nullable DataContainerCodecPrototype augmentationByClass(final @NonNull Class childClass) { - final DataContainerCodecPrototype firstTry = augmentationByClassOrEquivalentClass(childClass); - if (firstTry != null) { - return firstTry; + DataContainerCodecPrototype lookup = augmentationByClassOrEquivalentClass(childClass); + if (lookup != null || !isPotentialAugmentation(childClass)) { + return lookup; } + + // Attempt to reload all augmentations using TCCL and lookup again reloadAllAugmentations(); + lookup = augmentationByClassOrEquivalentClass(childClass); + if (lookup != null) { + return lookup; + } + + // Still no result, this can be caused by TCCL not being set up properly -- try the class's ClassLoader + // if it is present; + final ClassLoader loader = childClass.getClassLoader(); + if (loader == null) { + return null; + } + + LOG.debug("Class {} not loaded via TCCL, attempting to recover", childClass); + ClassLoaderUtils.runWithClassLoader(loader, this::reloadAllAugmentations); return augmentationByClassOrEquivalentClass(childClass); } + private boolean isPotentialAugmentation(final Class childClass) { + final JavaTypeName name = JavaTypeName.create(childClass); + for (Type type : possibleAugmentations.values()) { + if (name.equals(type.getIdentifier())) { + return true; + } + } + return false; + } + private @Nullable DataContainerCodecPrototype augmentationByClassOrEquivalentClass( final @NonNull Class childClass) { - final DataContainerCodecPrototype childProto = byStreamAugmented.get(childClass); + // Perform a single load, so we can reuse it if we end up going to the reflection-based slow path + final ImmutableMap, DataContainerCodecPrototype> local = augmentations.byStream; + final DataContainerCodecPrototype childProto = local.get(childClass); if (childProto != null) { return childProto; } /* - * It is potentially mismatched valid augmentation - we look up equivalent augmentation - * using reflection and walk all stream child and compare augmenations classes if they are - * equivalent. - * - * FIXME: Cache mapping of mismatched augmentation to real one, to speed up lookup. + * It is potentially mismatched valid augmentation - we look up equivalent augmentation using reflection + * and walk all stream child and compare augmentations classes if they are equivalent. When we find a match + * we'll cache it so we do not need to perform reflection operations again. */ + final DataContainerCodecPrototype mismatched = mismatchedAugmented.get(childClass); + if (mismatched != null) { + return mismatched; + } + @SuppressWarnings("rawtypes") final Class augTarget = BindingReflections.findAugmentationTarget((Class) childClass); if (getBindingClass().equals(augTarget)) { - for (final DataContainerCodecPrototype realChild : byStreamAugmented.values()) { + for (final DataContainerCodecPrototype realChild : local.values()) { if (Augmentation.class.isAssignableFrom(realChild.getBindingClass()) && BindingReflections.isSubstitutionFor(childClass, realChild.getBindingClass())) { - return realChild; + return cacheMismatched(childClass, realChild); } } } + LOG.trace("Failed to resolve {} as a valid augmentation in {}", childClass, this); return null; } + private synchronized DataContainerCodecPrototype cacheMismatched(final Class childClass, + final DataContainerCodecPrototype prototype) { + // Original access was unsynchronized, we need to perform additional checking + final ImmutableMap, DataContainerCodecPrototype> local = mismatchedAugmented; + final DataContainerCodecPrototype existing = local.get(childClass); + if (existing != null) { + return existing; + } + + final Builder, DataContainerCodecPrototype> builder = ImmutableMap.builderWithExpectedSize( + local.size() + 1); + builder.putAll(local); + builder.put(childClass, prototype); + + mismatchedAugmented = builder.build(); + LOG.trace("Cached mismatched augmentation {} -> {} in {}", childClass, prototype, this); + return prototype; + } + private DataContainerCodecPrototype getAugmentationPrototype(final Type value) { final ClassLoadingStrategy loader = factory().getRuntimeContext().getStrategy(); @SuppressWarnings("rawtypes") @@ -333,27 +516,13 @@ abstract class DataObjectCodecContext childContext = byMethod.get(method).get(); - @SuppressWarnings("unchecked") - final java.util.Optional> domChild = domData.getChild(childContext.getDomPathArgument()); - if (domChild.isPresent()) { - return childContext.deserializeObject(domChild.get()); - } else if (childContext instanceof LeafNodeCodecContext) { - return ((LeafNodeCodecContext)childContext).defaultObject(); - } else { - return null; - } - } - @SuppressWarnings("checkstyle:illegalCatch") protected final D createBindingProxy(final NormalizedNodeContainer node) { try { - return (D) proxyConstructor.invokeExact((InvocationHandler)new LazyDataObject<>(this, node)); + return (D) proxyConstructor.invokeExact(node); } catch (final Throwable e) { Throwables.throwIfUnchecked(e); - throw new RuntimeException(e); + throw new IllegalStateException(e); } } @@ -374,8 +543,8 @@ abstract class DataObjectCodecContext value : byStreamAugmented.values()) { - final java.util.Optional> augData = data.getChild(value.getYangArg()); + for (final DataContainerCodecPrototype value : augmentations.byStream.values()) { + final Optional> augData = data.getChild(value.getYangArg()); if (augData.isPresent()) { map.put(value.getBindingClass(), value.get().deserializeObject(augData.get())); } @@ -383,10 +552,6 @@ abstract class DataObjectCodecContext getHashCodeAndEqualsMethods() { - return byMethod.keySet(); - } - @Override public InstanceIdentifier.PathArgument deserializePathArgument(final YangInstanceIdentifier.PathArgument arg) { checkArgument(getDomPathArgument().equals(arg));