From 397b1239e991457b0e0605bec78faf2a5c2f73b8 Mon Sep 17 00:00:00 2001 From: Martin Ciglan Date: Mon, 5 Jun 2017 16:27:47 +0200 Subject: [PATCH] Binding2 runtime - Codecs impl #2 - NodeCodecContext & relatives TODO: test coverage Change-Id: I024618f2b80207e47eec85819390868ee34b6407 Signed-off-by: Martin Ciglan --- .../codec/impl/MissingSchemaException.java | 57 +++ .../base/DataContainerCodecContext.java | 219 ++++++++++ .../base/DataContainerCodecPrototype.java | 133 ++++++ .../base/IncorrectNestingException.java | 45 +++ .../codec/impl/context/base/LazyTreeNode.java | 235 +++++++++++ .../context/base/LeafNodeCodecContext.java | 250 ++++++++++++ ...issingClassInLoadingStrategyException.java | 39 ++ .../base/MissingSchemaForClassException.java | 55 +++ .../impl/context/base/NodeCodecContext.java | 107 +++++ .../context/base/NodeContextSupplier.java | 24 ++ .../context/base/TreeNodeCodecContext.java | 381 ++++++++++++++++++ 11 files changed, 1545 insertions(+) create mode 100644 binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/MissingSchemaException.java create mode 100644 binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/context/base/DataContainerCodecContext.java create mode 100644 binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/context/base/DataContainerCodecPrototype.java create mode 100644 binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/context/base/IncorrectNestingException.java create mode 100644 binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/context/base/LazyTreeNode.java create mode 100644 binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/context/base/LeafNodeCodecContext.java create mode 100644 binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/context/base/MissingClassInLoadingStrategyException.java create mode 100644 binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/context/base/MissingSchemaForClassException.java create mode 100644 binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/context/base/NodeCodecContext.java create mode 100644 binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/context/base/NodeContextSupplier.java create mode 100644 binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/context/base/TreeNodeCodecContext.java diff --git a/binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/MissingSchemaException.java b/binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/MissingSchemaException.java new file mode 100644 index 0000000000..33e55c6db9 --- /dev/null +++ b/binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/MissingSchemaException.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2017 Pantheon Technologies s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.mdsal.binding.javav2.dom.codec.impl; + +import com.google.common.base.Preconditions; +import java.util.Set; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; + +/** + * Thrown when codec was used with data which are not modeled + * and available in schema used by codec. + */ +public class MissingSchemaException extends IllegalArgumentException { + + private static final long serialVersionUID = 1L; + + protected MissingSchemaException(final String msg) { + super(msg); + } + + protected MissingSchemaException(final String msg, final Throwable cause) { + super(msg, cause); + } + + private static MissingSchemaException create(final String format, final Object... args) { + return new MissingSchemaException(String.format(format, args)); + } + + public static void checkModulePresent(final SchemaContext schemaContext, final QName name) { + if(schemaContext.findModuleByNamespaceAndRevision(name.getNamespace(), name.getRevision()) == null) { + throw MissingSchemaException.create("Module %s is not present in current schema context.",name.getModule()); + } + } + + public static void checkModulePresent(final SchemaContext schemaContext, final YangInstanceIdentifier.PathArgument + child) { + checkModulePresent(schemaContext, extractName(child)); + } + + private static QName extractName(final PathArgument child) { + if(child instanceof YangInstanceIdentifier.AugmentationIdentifier) { + final Set children = ((YangInstanceIdentifier.AugmentationIdentifier) child).getPossibleChildNames(); + Preconditions.checkArgument(!children.isEmpty(),"Augmentation without childs must not be used in data"); + return children.iterator().next(); + } + return child.getNodeType(); + } +} diff --git a/binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/context/base/DataContainerCodecContext.java b/binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/context/base/DataContainerCodecContext.java new file mode 100644 index 0000000000..333ef2a276 --- /dev/null +++ b/binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/context/base/DataContainerCodecContext.java @@ -0,0 +1,219 @@ +/* + * Copyright (c) 2017 Pantheon Technologies s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.mdsal.binding.javav2.dom.codec.impl.context.base; + +import com.google.common.annotations.Beta; +import com.google.common.base.Optional; +import com.google.common.collect.ImmutableCollection; +import java.io.IOException; +import java.util.List; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.opendaylight.mdsal.binding.javav2.dom.codec.api.codecs.BindingNormalizedNodeCachingCodec; +import org.opendaylight.mdsal.binding.javav2.dom.codec.impl.MissingSchemaException; +import org.opendaylight.mdsal.binding.javav2.spec.base.TreeArgument; +import org.opendaylight.mdsal.binding.javav2.spec.base.TreeNode; +import org.opendaylight.mdsal.binding.javav2.spec.runtime.BindingStreamEventWriter; +import org.opendaylight.mdsal.binding.javav2.spec.runtime.TreeNodeSerializer; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.common.QNameModule; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; +import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter; +import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNormalizedNodeStreamWriter; +import org.opendaylight.yangtools.yang.data.impl.schema.NormalizedNodeResult; +import sun.reflect.generics.reflectiveObjects.NotImplementedException; + +@Beta +abstract class DataContainerCodecContext extends NodeCodecContext { + + private final DataContainerCodecPrototype prototype; + private volatile TreeNodeSerializer eventStreamSerializer; + + protected DataContainerCodecContext(final DataContainerCodecPrototype prototype) { + this.prototype = prototype; + } + + @Nonnull + @Override + public final T getSchema() { + return prototype.getSchema(); + } + + protected final QNameModule namespace() { + return prototype.getNamespace(); + } + + protected final CodecContextFactory factory() { + return prototype.getFactory(); + } + + @Override + protected YangInstanceIdentifier.PathArgument getDomPathArgument() { + return prototype.getYangArg(); + } + + /** + * Returns nested node context using supplied YANG Instance Identifier + * + * @param arg Yang Instance Identifier Argument + * @return Context of child + * @throws IllegalArgumentException If supplied argument does not represent valid child. + */ + @Nonnull + @Override + public abstract NodeCodecContext yangPathArgumentChild(final YangInstanceIdentifier.PathArgument arg); + + /** + * Returns nested node context using supplied Binding Instance Identifier + * and adds YANG instance identifiers to supplied list. + * + * @param arg Binding Instance Identifier Argument + * @return Context of child or null if supplied {@code arg} does not represent valid child. + * @throws IllegalArgumentException If supplied argument does not represent valid child. + */ + @SuppressWarnings("unchecked") + @Nonnull + @Override + public DataContainerCodecContext bindingPathArgumentChild(@Nonnull final TreeArgument arg, + final List builder) { + final DataContainerCodecContext child = streamChild((Class) arg.getType()); + if (builder != null) { + child.addYangPathArgument(arg,builder); + } + return child; + } + + /** + * Returns de-serialized Binding Path Argument from YANG instance identifier. + * + * @param domArg input path argument + * @return returns binding path argument + */ + @SuppressWarnings("rawtypes") + protected TreeArgument getBindingPathArgument(final YangInstanceIdentifier.PathArgument domArg) { + return bindingArg(); + } + + @SuppressWarnings("rawtypes") + protected final TreeArgument bindingArg() { + return prototype.getBindingArg(); + } + + @Nonnull + @SuppressWarnings("unchecked") + @Override + public final Class getBindingClass() { + return Class.class.cast(prototype.getBindingClass()); + } + + /** + * Returns child context as if it was walked by + * {@link BindingStreamEventWriter}. This means that to enter case, one + * must issue getChild(ChoiceClass).getChild(CaseClass). + * + * @param childClass input child class + * @return Context of child node or null, if supplied class is not subtree child + * @throws IllegalArgumentException If supplied child class is not valid in specified context. + */ + @Nonnull + @Override + public abstract DataContainerCodecContext streamChild(@Nonnull final Class childClass) throws IllegalArgumentException; + + /** + * Returns child context as if it was walked by + * {@link BindingStreamEventWriter}. This means that to enter case, one + * must issue getChild(ChoiceClass).getChild(CaseClass). + * + * @param childClass input child class + * @return Context of child or Optional absent is supplied class is not applicable in context. + */ + @Override + public abstract Optional> possibleStreamChild(@Nonnull + final Class childClass); + + @Override + public String toString() { + return getClass().getSimpleName() + " [" + prototype.getBindingClass() + "]"; + } + + @Nonnull + @Override + public BindingNormalizedNodeCachingCodec createCachingCodec( + @Nonnull final ImmutableCollection> cacheSpecifier) { + + //TODO: implement in cache-related patches to come + throw new NotImplementedException(); + } + + BindingStreamEventWriter createWriter(final NormalizedNodeStreamWriter domWriter) { + + //TODO: streamWriter to come + throw new NotImplementedException(); + } + + @Nonnull + protected final V childNonNull(@Nullable final V nullable, final YangInstanceIdentifier.PathArgument child, + final String message, final Object... args) { + if (nullable != null) { + return nullable; + } + MissingSchemaException.checkModulePresent(factory().getRuntimeContext().getSchemaContext(), child); + throw IncorrectNestingException.create(message, args); + } + + @Nonnull + protected final V childNonNull(@Nullable final V nullable, final QName child, final String message, + final Object... args) { + if (nullable != null) { + return nullable; + } + MissingSchemaException.checkModulePresent(factory().getRuntimeContext().getSchemaContext(), child); + throw IncorrectNestingException.create(message, args); + } + + @Nonnull + protected final V childNonNull(@Nullable final V nullable, final Class childClass, final String message, + final Object... args) { + if (nullable != null) { + return nullable; + } + MissingSchemaForClassException.check(factory().getRuntimeContext(), childClass); + MissingClassInLoadingStrategyException.check(factory().getRuntimeContext().getStrategy(), childClass); + throw IncorrectNestingException.create(message, args); + } + + TreeNodeSerializer eventStreamSerializer() { + if(eventStreamSerializer == null) { + eventStreamSerializer = factory().getEventStreamSerializer(getBindingClass()); + } + return eventStreamSerializer; + } + + @Nonnull + @Override + public NormalizedNode serialize(@Nonnull final D data) { + final NormalizedNodeResult result = new NormalizedNodeResult(); + // We create DOM stream writer which produces normalized nodes + final NormalizedNodeStreamWriter domWriter = ImmutableNormalizedNodeStreamWriter.from(result); + writeAsNormalizedNode(data, domWriter); + return result.getResult(); + } + + @Override + public void writeAsNormalizedNode(final D data, final NormalizedNodeStreamWriter writer) { + try { + eventStreamSerializer().serialize(data, createWriter(writer)); + } catch (final IOException e) { + throw new IllegalStateException("Failed to serialize Binding DTO",e); + } + } + +} \ No newline at end of file diff --git a/binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/context/base/DataContainerCodecPrototype.java b/binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/context/base/DataContainerCodecPrototype.java new file mode 100644 index 0000000000..c4a4f60d91 --- /dev/null +++ b/binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/context/base/DataContainerCodecPrototype.java @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2017 Pantheon Technologies s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.mdsal.binding.javav2.dom.codec.impl.context.base; + +import com.google.common.annotations.Beta; +import com.google.common.base.Preconditions; +import com.google.common.collect.Iterables; +import javax.annotation.Nonnull; +import javax.annotation.concurrent.GuardedBy; +import org.opendaylight.mdsal.binding.javav2.dom.codec.impl.context.base.NodeCodecContext.CodecContextFactory; +import org.opendaylight.mdsal.binding.javav2.spec.base.Item; +import org.opendaylight.mdsal.binding.javav2.spec.base.TreeRoot; +import org.opendaylight.yangtools.yang.common.QNameModule; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument; +import org.opendaylight.yangtools.yang.model.api.AugmentationSchema; +import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode; +import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; +import org.opendaylight.yangtools.yang.model.api.NotificationDefinition; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; +import sun.reflect.generics.reflectiveObjects.NotImplementedException; + +@Beta +final class DataContainerCodecPrototype implements NodeContextSupplier { + + private final T schema; + private final QNameModule namespace; + private final CodecContextFactory factory; + private final Class bindingClass; + private final Item bindingArg; + private final YangInstanceIdentifier.PathArgument yangArg; + private volatile DataContainerCodecContext instance = null; + + @SuppressWarnings({"rawtypes", "unchecked"}) + private DataContainerCodecPrototype(final Class cls, final YangInstanceIdentifier.PathArgument arg, final T nodeSchema, + final CodecContextFactory factory) { + this.bindingClass = Preconditions.checkNotNull(cls); + this.yangArg = Preconditions.checkNotNull(arg); + this.schema = Preconditions.checkNotNull(nodeSchema); + this.factory = Preconditions.checkNotNull(factory); + + this.bindingArg = new Item(bindingClass); + + if (arg instanceof AugmentationIdentifier) { + this.namespace = Iterables.getFirst(((AugmentationIdentifier) arg).getPossibleChildNames(), null).getModule(); + } else { + this.namespace = arg.getNodeType().getModule(); + } + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + static DataContainerCodecPrototype from(final Class cls, final T schema, + final CodecContextFactory factory) { + return new DataContainerCodecPrototype(cls, NodeIdentifier.create(schema.getQName()), schema, factory); + } + + static DataContainerCodecPrototype rootPrototype(final CodecContextFactory factory) { + final SchemaContext schema = factory.getRuntimeContext().getSchemaContext(); + final NodeIdentifier arg = NodeIdentifier.create(schema.getQName()); + return new DataContainerCodecPrototype<>(TreeRoot.class, arg, schema, factory); + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + static DataContainerCodecPrototype from(final Class augClass, final AugmentationIdentifier arg, + final AugmentationSchema schema, final CodecContextFactory factory) { + return new DataContainerCodecPrototype(augClass, arg, schema, factory); + } + + static DataContainerCodecPrototype from(final Class augClass, final NotificationDefinition schema, final CodecContextFactory factory) { + final PathArgument arg = NodeIdentifier.create(schema.getQName()); + return new DataContainerCodecPrototype<>(augClass, arg, schema, factory); + } + + protected T getSchema() { + return schema; + } + + protected QNameModule getNamespace() { + return namespace; + } + + protected CodecContextFactory getFactory() { + return factory; + } + + protected Class getBindingClass() { + return bindingClass; + } + + protected Item getBindingArg() { + return bindingArg; + } + + protected YangInstanceIdentifier.PathArgument getYangArg() { + return yangArg; + } + + @Nonnull + @Override + public DataContainerCodecContext get() { + DataContainerCodecContext tmp = instance; + if (tmp == null) { + synchronized (this) { + tmp = instance; + if (tmp == null) { + tmp = createInstance(); + instance = tmp; + } + } + } + + return tmp; + } + + @GuardedBy("this") + @SuppressWarnings({"rawtypes", "unchecked"}) + protected DataContainerCodecContext createInstance() { + //TODO - implement it + throw new NotImplementedException(); + } + + boolean isChoice() { + return schema instanceof ChoiceSchemaNode; + } +} \ No newline at end of file diff --git a/binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/context/base/IncorrectNestingException.java b/binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/context/base/IncorrectNestingException.java new file mode 100644 index 0000000000..16a4385c84 --- /dev/null +++ b/binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/context/base/IncorrectNestingException.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2017 Pantheon Technologies s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.mdsal.binding.javav2.dom.codec.impl.context.base; + +import com.google.common.annotations.Beta; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * Thrown where incorrect nesting of data structures was detected + * and was caused by user. + */ +@Beta +class IncorrectNestingException extends IllegalArgumentException { + + private static final long serialVersionUID = 1L; + + protected IncorrectNestingException(final String message) { + super(message); + } + + public static IncorrectNestingException create(final String message, final Object... args) { + return new IncorrectNestingException(String.format(message, args)); + } + + public static void check(final boolean check, final String message, final Object... args) { + if (!check) { + throw IncorrectNestingException.create(message, args); + } + } + + @Nonnull + public static V checkNonNull(@Nullable final V nullable, final String message, final Object... args) { + if (nullable != null) { + return nullable; + } + throw IncorrectNestingException.create(message, args); + } +} \ No newline at end of file diff --git a/binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/context/base/LazyTreeNode.java b/binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/context/base/LazyTreeNode.java new file mode 100644 index 0000000000..288622ebab --- /dev/null +++ b/binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/context/base/LazyTreeNode.java @@ -0,0 +1,235 @@ +/* + * Copyright (c) 2017 Pantheon Technologies s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.mdsal.binding.javav2.dom.codec.impl.context.base; + +import com.google.common.annotations.Beta; +import com.google.common.base.MoreObjects; +import com.google.common.base.MoreObjects.ToStringHelper; +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableMap; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.Arrays; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import org.opendaylight.mdsal.binding.javav2.dom.codec.api.AugmentationReader; +import org.opendaylight.mdsal.binding.javav2.runtime.reflection.BindingReflections; +import org.opendaylight.mdsal.binding.javav2.spec.base.TreeNode; +import org.opendaylight.mdsal.binding.javav2.spec.structural.Augmentable; +import org.opendaylight.mdsal.binding.javav2.spec.structural.Augmentation; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@Beta +class LazyTreeNode implements InvocationHandler, AugmentationReader { + + private static final Logger LOG = LoggerFactory.getLogger(LazyTreeNode.class); + private static final String GET_IMPLEMENTED_INTERFACE = "implementedInterface"; + private static final String TO_STRING = "toString"; + private static final String EQUALS = "equals"; + private static final String GET_AUGMENTATION = "getAugmentation"; + private static final String HASHCODE = "hashCode"; + private static final String AUGMENTATIONS = "augmentations"; + private static final Object NULL_VALUE = new Object(); + + private final ConcurrentHashMap cachedData = new ConcurrentHashMap<>(); + private final NormalizedNodeContainer> data; + private final TreeNodeCodecContext context; + + private volatile ImmutableMap>, Augmentation> cachedAugmentations = null; + private volatile Integer cachedHashcode = null; + + @SuppressWarnings({ "rawtypes", "unchecked" }) + LazyTreeNode(final TreeNodeCodecContext ctx, final NormalizedNodeContainer data) { + this.context = Preconditions.checkNotNull(ctx, "Context must not be null"); + this.data = Preconditions.checkNotNull(data, "Data must not be null"); + } + + @Override + public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable { + if (method.getParameterTypes().length == 0) { + final String name = method.getName(); + if (GET_IMPLEMENTED_INTERFACE.equals(name)) { + return context.getBindingClass(); + } else if (TO_STRING.equals(name)) { + return bindingToString(); + } else if (HASHCODE.equals(name)) { + return bindingHashCode(); + } else if (AUGMENTATIONS.equals(name)) { + return getAugmentationsImpl(); + } + return getBindingData(method); + } else if (GET_AUGMENTATION.equals(method.getName())) { + return getAugmentationImpl((Class) args[0]); + } else if (EQUALS.equals(method.getName())) { + return bindingEquals(args[0]); + } + throw new UnsupportedOperationException("Unsupported method " + method); + } + + private boolean bindingEquals(final Object other) { + if (other == null) { + return false; + } + if (!context.getBindingClass().isAssignableFrom(other.getClass())) { + return false; + } + try { + for (final Method m : context.getHashCodeAndEqualsMethods()) { + final Object thisValue = getBindingData(m); + final Object otherValue = m.invoke(other); + /* + * added for valid byte array comparison, when list key type is binary + * deepEquals is not used since it does excessive amount of instanceof calls. + */ + if (thisValue instanceof byte[] && otherValue instanceof byte[]) { + if (!Arrays.equals((byte[]) thisValue, (byte[]) otherValue)) { + return false; + } + } else if (!Objects.equals(thisValue, otherValue)){ + return false; + } + } + + if (Augmentable.class.isAssignableFrom(context.getBindingClass())) { + if (!getAugmentationsImpl().equals(getAllAugmentations(other))) { + return false; + } + } + } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { + LOG.warn("Can not determine equality of {} and {}", this, other, e); + return false; + } + return true; + } + + private static Map>, Augmentation> getAllAugmentations(final Object dataObject) { + if (dataObject instanceof AugmentationReader) { + return ((AugmentationReader) dataObject).getAugmentations(dataObject); + } else if (dataObject instanceof Augmentable){ + return BindingReflections.getAugmentations((Augmentable) dataObject); + } + + throw new IllegalArgumentException("Unable to get all augmentations from " + dataObject); + } + + private Integer bindingHashCode() { + final Integer ret = cachedHashcode; + if (ret != null) { + return ret; + } + + final int prime = 31; + int result = 1; + for (final Method m : context.getHashCodeAndEqualsMethods()) { + final Object value = getBindingData(m); + result = prime * result + Objects.hashCode(value); + } + if (Augmentable.class.isAssignableFrom(context.getBindingClass())) { + result = prime * result + (getAugmentationsImpl().hashCode()); + } + cachedHashcode = result; + return result; + } + + private Object getBindingData(final Method method) { + Object cached = cachedData.get(method); + if (cached == null) { + final Object readedValue = context.getBindingChildValue(method, data); + if (readedValue == null) { + cached = NULL_VALUE; + } else { + cached = readedValue; + } + cachedData.putIfAbsent(method, cached); + } + + return cached == NULL_VALUE ? null : cached; + } + + private Map>, Augmentation> getAugmentationsImpl() { + ImmutableMap>, Augmentation> ret = cachedAugmentations; + if (ret == null) { + synchronized (this) { + ret = cachedAugmentations; + if (ret == null) { + ret = ImmutableMap.copyOf(context.getAllAugmentationsFrom(data)); + cachedAugmentations = ret; + } + } + } + + return ret; + } + + @Override + public Map>, Augmentation> getAugmentations(final Object obj) { + Preconditions.checkArgument(this == Proxy.getInvocationHandler(obj), + "Supplied object is not associated with this proxy handler"); + + return getAugmentationsImpl(); + } + + private Object getAugmentationImpl(final Class cls) { + final ImmutableMap>, Augmentation> aug = cachedAugmentations; + if (aug != null) { + return aug.get(cls); + } + Preconditions.checkNotNull(cls,"Supplied augmentation must not be null."); + + @SuppressWarnings({"unchecked","rawtypes"}) + final Optional> augCtx= context.possibleStreamChild((Class) cls); + if(augCtx.isPresent()) { + final Optional> augData = data.getChild(augCtx.get().getDomPathArgument()); + if (augData.isPresent()) { + return augCtx.get().deserialize(augData.get()); + } + } + return null; + } + + public String bindingToString() { + final ToStringHelper helper = MoreObjects.toStringHelper(context.getBindingClass()).omitNullValues(); + + for (final Method m :context.getHashCodeAndEqualsMethods()) { + helper.add(m.getName(), getBindingData(m)); + } + if (Augmentable.class.isAssignableFrom(context.getBindingClass())) { + helper.add("augmentations", getAugmentationsImpl()); + } + return helper.toString(); + } + + @Override + public int hashCode() { + return Objects.hash(this.context, this.data); + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final LazyTreeNode other = (LazyTreeNode) obj; + return Objects.equals(context, other.context) && Objects.equals(data, other.data); + } +} \ No newline at end of file diff --git a/binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/context/base/LeafNodeCodecContext.java b/binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/context/base/LeafNodeCodecContext.java new file mode 100644 index 0000000000..f6835b3292 --- /dev/null +++ b/binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/context/base/LeafNodeCodecContext.java @@ -0,0 +1,250 @@ +/* + * Copyright (c) 2017 Pantheon Technologies s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.mdsal.binding.javav2.dom.codec.impl.context.base; + +import com.google.common.annotations.Beta; +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableCollection; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Set; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.opendaylight.mdsal.binding.javav2.dom.codec.api.codecs.BindingNormalizedNodeCachingCodec; +import org.opendaylight.mdsal.binding.javav2.dom.codec.api.codecs.BindingTreeNodeCodec; +import org.opendaylight.mdsal.binding.javav2.spec.base.TreeArgument; +import org.opendaylight.mdsal.binding.javav2.spec.base.TreeNode; +import org.opendaylight.yangtools.concepts.Codec; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.schema.LeafNode; +import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode; +import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; +import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter; +import org.opendaylight.yangtools.yang.data.impl.codec.TypeDefinitionAwareCodec; +import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; +import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode; +import org.opendaylight.yangtools.yang.model.api.Module; +import org.opendaylight.yangtools.yang.model.api.ModuleImport; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; +import org.opendaylight.yangtools.yang.model.api.TypeDefinition; +import org.opendaylight.yangtools.yang.model.api.type.IdentityrefTypeDefinition; + +@Beta +final class LeafNodeCodecContext extends NodeCodecContext implements NodeContextSupplier { + + private final YangInstanceIdentifier.PathArgument yangIdentifier; + private final Codec valueCodec; + private final Method getter; + private final DataSchemaNode schema; + private final Object defaultObject; + + LeafNodeCodecContext(final DataSchemaNode schema, final Codec codec, final Method getter, + final SchemaContext schemaContext) { + this.yangIdentifier = new YangInstanceIdentifier.NodeIdentifier(schema.getQName()); + this.valueCodec = Preconditions.checkNotNull(codec); + this.getter = Preconditions.checkNotNull(getter); + this.schema = Preconditions.checkNotNull(schema); + + this.defaultObject = createDefaultObject(schema, valueCodec, schemaContext); + } + + private static Object createDefaultObject(final DataSchemaNode schema, final Codec codec, + final SchemaContext schemaContext) { + if (schema instanceof LeafSchemaNode) { + Object defaultValue = ((LeafSchemaNode) schema).getDefault(); + TypeDefinition type = ((LeafSchemaNode) schema).getType(); + if (defaultValue != null) { + if (type instanceof IdentityrefTypeDefinition) { + return qnameDomValueFromString(codec, schema, (String) defaultValue, schemaContext); + } + return domValueFromString(codec, type, defaultValue); + } + else { + while (type.getBaseType() != null && type.getDefaultValue() == null) { + type = type.getBaseType(); + } + + defaultValue = type.getDefaultValue(); + if (defaultValue != null) { + if (type instanceof IdentityrefTypeDefinition) { + return qnameDomValueFromString(codec, schema, (String) defaultValue, schemaContext); + } + return domValueFromString(codec, type, defaultValue); + } + } + } + return null; + } + + private static Object qnameDomValueFromString(final Codec codec, final DataSchemaNode schema, + final String defaultValue, final SchemaContext schemaContext) { + final int prefixEndIndex = defaultValue.indexOf(':'); + if (prefixEndIndex != -1) { + final String defaultValuePrefix = defaultValue.substring(0, prefixEndIndex); + + final Module module = schemaContext.findModuleByNamespaceAndRevision(schema.getQName().getNamespace(), + schema.getQName().getRevision()); + + if (module.getPrefix().equals(defaultValuePrefix)) { + return codec + .deserialize(QName.create(module.getQNameModule(), defaultValue.substring(prefixEndIndex + 1))); + } else { + final Set imports = module.getImports(); + for (final ModuleImport moduleImport : imports) { + if (moduleImport.getPrefix().equals(defaultValuePrefix)) { + final Module importedModule = schemaContext.findModuleByName(moduleImport.getModuleName(), + moduleImport.getRevision()); + return codec.deserialize(QName.create(importedModule.getQNameModule(), + defaultValue.substring(prefixEndIndex + 1))); + } + } + return null; + } + } + + return codec.deserialize(QName.create(schema.getQName(), defaultValue)); + } + + private static Object domValueFromString(final Codec codec, final TypeDefinition type, + final Object defaultValue) { + final TypeDefinitionAwareCodec typeDefAwareCodec = TypeDefinitionAwareCodec.from(type); + if (typeDefAwareCodec != null) { + final Object castedDefaultValue = typeDefAwareCodec.deserialize((String) defaultValue); + return codec.deserialize(castedDefaultValue); + } + // FIXME: BUG-4647 Refactor / redesign this to throw hard error, + // once BUG-4638 is fixed and will provide proper getDefaultValue implementation. + return null; + } + + @Override + protected YangInstanceIdentifier.PathArgument getDomPathArgument() { + return yangIdentifier; + } + + protected Codec getValueCodec() { + return valueCodec; + } + + @Nonnull + @Override + public D deserialize(@Nonnull final NormalizedNode normalizedNode) { + throw new UnsupportedOperationException("Leaf can not be deserialized to TreeNode"); + } + + @Nonnull + @Override + public NodeCodecContext get() { + return this; + } + + final Method getGetter() { + return getter; + } + + @Nonnull + @Override + public BindingTreeNodeCodec bindingPathArgumentChild(@Nonnull final TreeArgument arg, + final List builder) { + throw new IllegalArgumentException("Leaf does not have children"); + } + + @Nonnull + @Override + public BindingNormalizedNodeCachingCodec createCachingCodec( + @Nonnull final ImmutableCollection> cacheSpecifier) { + throw new UnsupportedOperationException("Leaves does not support caching codec."); + } + + @Nonnull + @Override + public Class getBindingClass() { + throw new UnsupportedOperationException("Leaf does not have DataObject representation"); + } + + @Nonnull + @Override + public NormalizedNode serialize(@Nonnull final D data) { + throw new UnsupportedOperationException("Separate serialization of leaf node is not supported."); + } + + @Override + public void writeAsNormalizedNode(final D data, final NormalizedNodeStreamWriter writer) { + throw new UnsupportedOperationException("Separate serialization of leaf node is not supported."); + } + + @Nonnull + @Override + public BindingTreeNodeCodec streamChild(@Nonnull final Class childClass) { + throw new IllegalArgumentException("Leaf does not have children"); + } + + @Override + public Optional> possibleStreamChild( + @Nonnull final Class childClass) { + throw new IllegalArgumentException("Leaf does not have children"); + } + + @Nonnull + @Override + public BindingTreeNodeCodec yangPathArgumentChild(final YangInstanceIdentifier.PathArgument child) { + throw new IllegalArgumentException("Leaf does not have children"); + } + + @Override + protected Object deserializeObject(final NormalizedNode normalizedNode) { + if (normalizedNode instanceof LeafNode) { + return valueCodec.deserialize(normalizedNode.getValue()); + } + if (normalizedNode instanceof LeafSetNode) { + @SuppressWarnings("unchecked") + final Collection> domValues = ((LeafSetNode) normalizedNode).getValue(); + final List result = new ArrayList<>(domValues.size()); + for (final LeafSetEntryNode valueNode : domValues) { + result.add(valueCodec.deserialize(valueNode.getValue())); + } + return result; + } + return null; + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Override + public TreeArgument deserializePathArgument(final YangInstanceIdentifier.PathArgument arg) { + Preconditions.checkArgument(getDomPathArgument().equals(arg)); + return null; + } + + @SuppressWarnings("rawtypes") + @Override + public YangInstanceIdentifier.PathArgument serializePathArgument(final TreeArgument arg) { + return getDomPathArgument(); + } + + @Nonnull + @Override + public DataSchemaNode getSchema() { + return schema; + } + + /** + * Return the default value object. + * + * @return The default value object, or null if the default value is not defined. + */ + @Nullable + Object defaultObject() { + return defaultObject; + } +} \ No newline at end of file diff --git a/binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/context/base/MissingClassInLoadingStrategyException.java b/binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/context/base/MissingClassInLoadingStrategyException.java new file mode 100644 index 0000000000..6296b99245 --- /dev/null +++ b/binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/context/base/MissingClassInLoadingStrategyException.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2017 Pantheon Technologies s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.mdsal.binding.javav2.dom.codec.impl.context.base; + +import com.google.common.annotations.Beta; +import org.opendaylight.mdsal.binding.javav2.dom.codec.impl.MissingSchemaException; +import org.opendaylight.mdsal.binding.javav2.generator.api.ClassLoadingStrategy; + +/** + * Thrown when user schema for supplied binding class is available in present schema context, but + * binding class itself is not known to codecs because backing class loading strategy did not + * provided it. + */ +@Beta +public class MissingClassInLoadingStrategyException extends MissingSchemaException { + + private static final long serialVersionUID = 1L; + + protected MissingClassInLoadingStrategyException(final String msg, final Throwable cause) { + super(msg, cause); + } + + public static void check(final ClassLoadingStrategy strategy, final Class childClass) { + try { + strategy.loadClass(childClass.getName()); + } catch (final ClassNotFoundException e) { + final String message = + String.format("User supplied class %s is not available in %s.", childClass.getName(), strategy); + throw new MissingClassInLoadingStrategyException(message, e); + } + } + +} \ No newline at end of file diff --git a/binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/context/base/MissingSchemaForClassException.java b/binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/context/base/MissingSchemaForClassException.java new file mode 100644 index 0000000000..e8131d282e --- /dev/null +++ b/binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/context/base/MissingSchemaForClassException.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2017 Pantheon Technologies s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.mdsal.binding.javav2.dom.codec.impl.context.base; + +import com.google.common.annotations.Beta; +import com.google.common.base.Preconditions; +import org.opendaylight.mdsal.binding.javav2.dom.codec.impl.MissingSchemaException; +import org.opendaylight.mdsal.binding.javav2.runtime.context.BindingRuntimeContext; +import org.opendaylight.mdsal.binding.javav2.spec.structural.Augmentation; + +/** + * Thrown when Java Binding class was used in data for which codec does not + * have schema. + * + * By serialization / deserialization of this exception {@link #getBindingClass()} + * will return null. + */ +@Beta +public class MissingSchemaForClassException extends MissingSchemaException { + + private static final long serialVersionUID = 1L; + + private final transient Class bindingClass; + + private MissingSchemaForClassException(final Class clz) { + super(String.format("Schema is not available for %s", clz)); + this.bindingClass = Preconditions.checkNotNull(clz); + } + + static MissingSchemaForClassException forClass(final Class clz) { + return new MissingSchemaForClassException(clz); + } + + public Class getBindingClass() { + return bindingClass; + } + + public static void check(final BindingRuntimeContext runtimeContext, final Class bindingClass) { + final Object schema; + if (Augmentation.class.isAssignableFrom(bindingClass)) { + schema = runtimeContext.getAugmentationDefinition(bindingClass); + } else { + schema = runtimeContext.getSchemaDefinition(bindingClass); + } + if (schema == null) { + throw MissingSchemaForClassException.forClass(bindingClass); + } + } +} \ No newline at end of file diff --git a/binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/context/base/NodeCodecContext.java b/binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/context/base/NodeCodecContext.java new file mode 100644 index 0000000000..196eb861dc --- /dev/null +++ b/binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/context/base/NodeCodecContext.java @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2017 Pantheon Technologies s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.mdsal.binding.javav2.dom.codec.impl.context.base; + +import com.google.common.annotations.Beta; +import com.google.common.collect.ImmutableMap; +import java.util.List; +import org.opendaylight.mdsal.binding.javav2.dom.codec.api.codecs.BindingTreeNodeCodec; +import org.opendaylight.mdsal.binding.javav2.runtime.context.BindingRuntimeContext; +import org.opendaylight.mdsal.binding.javav2.spec.base.IdentifiableItem; +import org.opendaylight.mdsal.binding.javav2.spec.base.TreeArgument; +import org.opendaylight.mdsal.binding.javav2.spec.base.TreeNode; +import org.opendaylight.mdsal.binding.javav2.spec.runtime.TreeNodeSerializer; +import org.opendaylight.yangtools.concepts.Codec; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; +import org.opendaylight.yangtools.yang.model.api.DataNodeContainer; +import org.opendaylight.yangtools.yang.model.api.ListSchemaNode; + +/** + * + * Location specific context for schema nodes, which contains codec specific + * information to properly serialize / deserialize from Java YANG Binding data + * to NormalizedNode data. + * + * Two core subtypes of codec context are available: + *
    + *
  • {@link LeafNodeCodecContext} - Context for nodes, which does not contain + * any nested YANG modeled substructures.
  • + *
  • {@link TreeNodeCodecContext} - Context for nodes, which does contain + * nested YANG modeled substructures. This context nodes contains context + * for children nodes.
  • + *
+ * + */ +@Beta +abstract class NodeCodecContext implements BindingTreeNodeCodec{ + /** + * Returns Yang Instance Identifier Path Argument of current node + * + * @return DOM Path Argument of node + */ + protected abstract YangInstanceIdentifier.PathArgument getDomPathArgument(); + + /** + * + * Immutable factory, which provides access to runtime context, + * create leaf nodes and provides path argument codecs. + *

+ * During lifetime of factory all calls for same arguments to method must return + * equal result (not necessary same instance of result). + * + */ + protected interface CodecContextFactory { + /** + * Returns immutable runtime context associated with this factory. + * @return runtime context + */ + BindingRuntimeContext getRuntimeContext(); + + /** + * Returns leaf nodes for supplied data container and parent class. + * + * @param type Binding type for which leaves should be loaded. + * @param schema Instantiated schema of binding type. + * @return Map of local name to leaf node context. + */ + ImmutableMap> getLeafNodes(Class type, DataNodeContainer schema); + + /** + * Returns Path argument codec for list item + * + * @param type Type of list item + * @param schema Schema of list item + * @return Path argument codec for supplied list item. + */ + Codec> getPathArgumentCodec(Class type, + ListSchemaNode schema); + + TreeNodeSerializer getEventStreamSerializer(Class type); + } + + /** + * + * Serializes supplied Binding Path Argument + * and adds all necessary YANG instance identifiers to supplied list. + * + * @param arg Binding Path Argument + * @param builder DOM Path argument. + */ + @SuppressWarnings("rawtypes") + protected void addYangPathArgument(final TreeArgument arg, + final List builder) { + if (builder != null) { + builder.add(getDomPathArgument()); + } + } + + protected abstract Object deserializeObject(NormalizedNode normalizedNode); +} \ No newline at end of file diff --git a/binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/context/base/NodeContextSupplier.java b/binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/context/base/NodeContextSupplier.java new file mode 100644 index 0000000000..b7ae443ca6 --- /dev/null +++ b/binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/context/base/NodeContextSupplier.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2017 Pantheon Technologies s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.mdsal.binding.javav2.dom.codec.impl.context.base; + +import com.google.common.annotations.Beta; +import com.google.common.base.Supplier; +import javax.annotation.Nonnull; + +/** + * Type capture of an entity producing NodeCodecContexts. + */ +@Beta +interface NodeContextSupplier extends Supplier> { + + @Override + @Nonnull + NodeCodecContext get(); +} \ No newline at end of file diff --git a/binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/context/base/TreeNodeCodecContext.java b/binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/context/base/TreeNodeCodecContext.java new file mode 100644 index 0000000000..94c2810d9a --- /dev/null +++ b/binding2/mdsal-binding2-dom-codec/src/main/java/org/opendaylight/mdsal/binding/javav2/dom/codec/impl/context/base/TreeNodeCodecContext.java @@ -0,0 +1,381 @@ +/* + * Copyright (c) 2017 Pantheon Technologies s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.mdsal.binding.javav2.dom.codec.impl.context.base; + +import com.google.common.annotations.Beta; +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; +import com.google.common.base.Throwables; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSortedMap; +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.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.SortedMap; +import java.util.TreeMap; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.opendaylight.mdsal.binding.javav2.generator.api.ClassLoadingStrategy; +import org.opendaylight.mdsal.binding.javav2.model.api.Type; +import org.opendaylight.mdsal.binding.javav2.runtime.reflection.BindingReflections; +import org.opendaylight.mdsal.binding.javav2.spec.base.TreeArgument; +import org.opendaylight.mdsal.binding.javav2.spec.base.TreeNode; +import org.opendaylight.mdsal.binding.javav2.spec.structural.Augmentable; +import org.opendaylight.mdsal.binding.javav2.spec.structural.Augmentation; +import org.opendaylight.mdsal.binding.javav2.spec.structural.AugmentationHolder; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument; +import org.opendaylight.yangtools.yang.data.api.schema.AugmentationNode; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer; +import org.opendaylight.yangtools.yang.model.api.AugmentationSchema; +import org.opendaylight.yangtools.yang.model.api.DataNodeContainer; +import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; +import org.opendaylight.yangtools.yang.model.util.SchemaNodeUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@Beta +abstract class TreeNodeCodecContext + extends DataContainerCodecContext { + + private static final Logger LOG = LoggerFactory.getLogger(TreeNodeCodecContext.class); + private static final MethodType CONSTRUCTOR_TYPE = MethodType.methodType(void.class, InvocationHandler.class); + private static final MethodType TREENODE_TYPE = MethodType.methodType(TreeNode.class, InvocationHandler.class); + private static final Comparator METHOD_BY_ALPHABET = Comparator.comparing(Method::getName); + + 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<>(); + + + protected TreeNodeCodecContext(final DataContainerCodecPrototype prototype) { + super(prototype); + + this.leafChild = factory().getLeafNodes(getBindingClass(), getSchema()); + + final Map, Method> clsToMethod = BindingReflections.getChildrenClassToMethod(getBindingClass()); + + final Map byYangBuilder = new HashMap<>(); + final SortedMap byMethodBuilder = new TreeMap<>(METHOD_BY_ALPHABET); + 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); + byYangBuilder.put(leaf.getDomPathArgument(), leaf); + } + + for (final Entry, Method> childDataObj : clsToMethod.entrySet()) { + final DataContainerCodecPrototype childProto = loadChildPrototype(childDataObj.getKey()); + byMethodBuilder.put(childDataObj.getValue(), childProto); + byStreamClassBuilder.put(childProto.getBindingClass(), childProto); + byYangBuilder.put(childProto.getYangArg(), childProto); + //TODO: get cases in consideration - finish in patches to come + //if (childProto.isChoice()) { + } + this.byMethod = ImmutableSortedMap.copyOfSorted(byMethodBuilder); + this.byYang = ImmutableMap.copyOf(byYangBuilder); + this.byStreamClass = ImmutableMap.copyOf(byStreamClassBuilder); + byBindingArgClassBuilder.putAll(byStreamClass); + this.byBindingArgClass = ImmutableMap.copyOf(byBindingArgClassBuilder); + + + if (Augmentable.class.isAssignableFrom(getBindingClass())) { + this.possibleAugmentations = factory().getRuntimeContext().getAvailableAugmentationTypes(getSchema()); + } else { + this.possibleAugmentations = ImmutableMap.of(); + } + reloadAllAugmentations(); + + final Class proxyClass = Proxy.getProxyClass(getBindingClass().getClassLoader(), new Class[] { getBindingClass(), AugmentationHolder.class }); + try { + proxyConstructor = MethodHandles.publicLookup().findConstructor(proxyClass, CONSTRUCTOR_TYPE) + .asType(TREENODE_TYPE); + } catch (NoSuchMethodException | IllegalAccessException e) { + throw new IllegalStateException("Failed to find contructor for class " + proxyClass); + } + } + + private void reloadAllAugmentations() { + for (final Entry augment : possibleAugmentations.entrySet()) { + final DataContainerCodecPrototype augProto = getAugmentationPrototype(augment.getValue()); + if (augProto != null) { + byYangAugmented.putIfAbsent(augProto.getYangArg(), augProto); + byStreamAugmented.putIfAbsent(augProto.getBindingClass(), augProto); + } + } + } + + @Nonnull + @SuppressWarnings("unchecked") + @Override + public DataContainerCodecContext streamChild(@Nonnull final Class childClass) { + final DataContainerCodecPrototype childProto = streamChildPrototype(childClass); + return (DataContainerCodecContext) childNonNull(childProto, childClass, " Child %s is not valid child.", + childClass).get(); + } + + private final DataContainerCodecPrototype streamChildPrototype(final Class childClass) { + final DataContainerCodecPrototype childProto = byStreamClass.get(childClass); + if (childProto != null) { + return childProto; + } + if (Augmentation.class.isAssignableFrom(childClass)) { + return augmentationByClass(childClass); + } + return null; + } + + @SuppressWarnings("unchecked") + @Override + public Optional> possibleStreamChild( + @Nonnull final Class childClass) { + final DataContainerCodecPrototype childProto = streamChildPrototype(childClass); + if (childProto != null) { + return Optional.of((DataContainerCodecContext) childProto.get()); + } + return Optional.absent(); + } + + @Nonnull + @Override + public DataContainerCodecContext bindingPathArgumentChild(final TreeArgument arg, + final List builder) { + + final Class argType = (Class) arg.getType(); + DataContainerCodecPrototype ctxProto = byBindingArgClass.get(argType); + if (ctxProto == null && Augmentation.class.isAssignableFrom(argType)) { + ctxProto = augmentationByClass(argType); + } + final DataContainerCodecContext context = + childNonNull(ctxProto, argType, "Class %s is not valid child of %s", argType, getBindingClass()).get(); + //TODO: get cases in consideration - finish in patches to come +// if (context instanceof ChoiceNodeCodecContext) { + context.addYangPathArgument(arg, builder); + return context; + } + + @Nonnull + @SuppressWarnings("unchecked") + @Override + public NodeCodecContext yangPathArgumentChild(final YangInstanceIdentifier.PathArgument arg) { + final NodeContextSupplier childSupplier; + if (arg instanceof NodeIdentifierWithPredicates) { + childSupplier = byYang.get(new NodeIdentifier(arg.getNodeType())); + } else if (arg instanceof AugmentationIdentifier) { + childSupplier = yangAugmentationChild((AugmentationIdentifier) arg); + } else { + childSupplier = byYang.get(arg); + } + + return (NodeCodecContext) 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); + return IncorrectNestingException.checkNonNull(value, "Leaf %s is not valid for %s", name, getBindingClass()); + } + + private DataContainerCodecPrototype loadChildPrototype(final Class childClass) { + final DataSchemaNode origDef = factory().getRuntimeContext().getSchemaDefinition(childClass); + // Direct instantiation or use in same module in which grouping + // was defined. + DataSchemaNode sameName; + try { + sameName = getSchema().getDataChildByName(origDef.getQName()); + } catch (final IllegalArgumentException e) { + sameName = null; + } + final DataSchemaNode childSchema; + if (sameName != null) { + // Exactly same schema node + if (origDef.equals(sameName)) { + childSchema = sameName; + // We check if instantiated node was added via uses + // statement and is instantiation of same grouping + } else if (origDef.equals(SchemaNodeUtils.getRootOriginalIfPossible(sameName))) { + childSchema = sameName; + } else { + // Node has same name, but clearly is different + childSchema = null; + } + } else { + // We are looking for instantiation via uses in other module + final QName instantiedName = QName.create(namespace(), origDef.getQName().getLocalName()); + final DataSchemaNode potential = getSchema().getDataChildByName(instantiedName); + // We check if it is really instantiated from same + // definition as class was derived + if (potential != null && origDef.equals(SchemaNodeUtils.getRootOriginalIfPossible(potential))) { + childSchema = potential; + } else { + childSchema = null; + } + } + final DataSchemaNode nonNullChild = + childNonNull(childSchema, childClass, "Node %s does not have child named %s", getSchema(), childClass); + return DataContainerCodecPrototype.from(childClass, nonNullChild, factory()); + } + + private DataContainerCodecPrototype yangAugmentationChild(final AugmentationIdentifier arg) { + final DataContainerCodecPrototype firstTry = byYangAugmented.get(arg); + if (firstTry != null) { + return firstTry; + } + if (possibleAugmentations.containsKey(arg)) { + reloadAllAugmentations(); + return byYangAugmented.get(arg); + } + return null; + } + + @Nullable + private DataContainerCodecPrototype augmentationByClass(@Nonnull final Class childClass) { + final DataContainerCodecPrototype firstTry = augmentationByClassOrEquivalentClass(childClass); + if (firstTry != null) { + return firstTry; + } + reloadAllAugmentations(); + return augmentationByClassOrEquivalentClass(childClass); + } + + @Nullable + private final DataContainerCodecPrototype augmentationByClassOrEquivalentClass(@Nonnull final Class childClass) { + final DataContainerCodecPrototype childProto = byStreamAugmented.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. + */ + @SuppressWarnings("rawtypes") + final Class augTarget = BindingReflections.findAugmentationTarget((Class) childClass); + if ((getBindingClass().equals(augTarget))) { + for (final DataContainerCodecPrototype realChild : byStreamAugmented.values()) { + if (Augmentation.class.isAssignableFrom(realChild.getBindingClass()) + && BindingReflections.isSubstitutionFor(childClass, realChild.getBindingClass())) { + return realChild; + } + } + } + return null; + } + + private DataContainerCodecPrototype getAugmentationPrototype(final Type value) { + final ClassLoadingStrategy loader = factory().getRuntimeContext().getStrategy(); + @SuppressWarnings("rawtypes") + final Class augClass; + try { + augClass = loader.loadClass(value); + } catch (final ClassNotFoundException e) { + LOG.debug("Failed to load augmentation prototype for {}. Will be retried when needed.", value, e); + return null; + } + + @SuppressWarnings("unchecked") + final Entry augSchema = factory().getRuntimeContext() + .getResolvedAugmentationSchema(getSchema(), augClass); + return DataContainerCodecPrototype.from(augClass, augSchema.getKey(), augSchema.getValue(), factory()); + } + + @SuppressWarnings("rawtypes") + Object getBindingChildValue(final Method method, final NormalizedNodeContainer domData) { + final NodeCodecContext childContext = byMethod.get(method).get(); + @SuppressWarnings("unchecked") + final 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; + } + } + + protected final D createBindingProxy(final NormalizedNodeContainer node) { + try { + return (D) proxyConstructor.invokeExact((InvocationHandler) new LazyTreeNode<>(this, node)); + } catch (final Throwable e) { + throw Throwables.propagate(e); + } + } + + @SuppressWarnings("unchecked") + public Map>, Augmentation> getAllAugmentationsFrom( + final NormalizedNodeContainer> data) { + + @SuppressWarnings("rawtypes") + final Map map = new HashMap<>(); + + for (final NormalizedNode childValue : data.getValue()) { + if (childValue instanceof AugmentationNode) { + final AugmentationNode augDomNode = (AugmentationNode) childValue; + final DataContainerCodecPrototype codecProto = yangAugmentationChild(augDomNode.getIdentifier()); + if (codecProto != null) { + final DataContainerCodecContext codec = codecProto.get(); + map.put(codec.getBindingClass(), codec.deserializeObject(augDomNode)); + } + } + } + for (final DataContainerCodecPrototype value : byStreamAugmented.values()) { + final Optional> augData = data.getChild(value.getYangArg()); + if (augData.isPresent()) { + map.put(value.getBindingClass(), value.get().deserializeObject(augData.get())); + } + } + return map; + } + + public Collection getHashCodeAndEqualsMethods() { + return byMethod.keySet(); + } + + @Override + public TreeArgument deserializePathArgument(final YangInstanceIdentifier.PathArgument arg) { + Preconditions.checkArgument(getDomPathArgument().equals(arg)); + return bindingArg(); + } + + @Override + public YangInstanceIdentifier.PathArgument serializePathArgument(final TreeArgument arg) { + Preconditions.checkArgument(bindingArg().equals(arg)); + return getDomPathArgument(); + } + +} \ No newline at end of file -- 2.36.6