From cd0e4ce77caa250e64dd7edabfbfc2952c5d6a85 Mon Sep 17 00:00:00 2001 From: Robert Varga Date: Sun, 14 Apr 2019 11:25:17 +0200 Subject: [PATCH] Add codec support for anyxml classes ForeignOpaqueData and ForeignOpaqueObject provide generalized support for proxying over to yang.data.api.ForeignDataNode, hence supporting anydata/anyxml in all possible object models. The codec tree is extended with BindingOpaqueObjectCodecTreeNode, which supports all OpaqueObjects, with the restriction that only AnyXmlNodes (i.e. those with DOMSource object model) are supported due to streaming restrictions in NormalizedNodeStreamWriter. JIRA: MDSAL-285 Change-Id: I9192008ae169cf49a98f64a218a33d470d134eae Signed-off-by: Robert Varga --- .../api/BindingOpaqueObjectCodecTreeNode.java | 17 ++ .../DataNodeContainerSerializerSource.java | 18 +-- .../gen/impl/DataObjectSerializerSource.java | 10 +- .../dom/codec/impl/BindingCodecContext.java | 20 ++- .../BindingNormalizedNodeCodecRegistry.java | 1 + .../codec/impl/DataObjectCodecContext.java | 10 +- .../dom/codec/impl/ForeignOpaqueData.java | 41 +++++ .../dom/codec/impl/ForeignOpaqueObject.java | 64 ++++++++ .../dom/codec/impl/LeafNodeCodecContext.java | 2 +- .../codec/impl/LeafSetNodeCodecContext.java | 2 +- .../codec/impl/OpaqueNodeCodecContext.java | 132 ++++++++++++++++ .../dom/codec/impl/ValueNodeCodecContext.java | 30 ++-- .../dom/codec/test/AnyxmlLeafTest.java | 145 ++++++++++++++++++ 13 files changed, 452 insertions(+), 40 deletions(-) create mode 100644 binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/api/BindingOpaqueObjectCodecTreeNode.java create mode 100644 binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/ForeignOpaqueData.java create mode 100644 binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/ForeignOpaqueObject.java create mode 100644 binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/OpaqueNodeCodecContext.java create mode 100644 binding/mdsal-binding-dom-codec/src/test/java/org/opendaylight/mdsal/binding/dom/codec/test/AnyxmlLeafTest.java diff --git a/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/api/BindingOpaqueObjectCodecTreeNode.java b/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/api/BindingOpaqueObjectCodecTreeNode.java new file mode 100644 index 0000000000..58818fbec7 --- /dev/null +++ b/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/api/BindingOpaqueObjectCodecTreeNode.java @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2019 PANTHEON.tech, 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.dom.codec.api; + +import com.google.common.annotations.Beta; +import org.opendaylight.yangtools.yang.binding.OpaqueObject; + +@Beta +public interface BindingOpaqueObjectCodecTreeNode> extends BindingObjectCodecTreeNode, + BindingNormalizedNodeCodec { + +} diff --git a/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/gen/impl/DataNodeContainerSerializerSource.java b/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/gen/impl/DataNodeContainerSerializerSource.java index 004cbadc3e..375fe00bd7 100644 --- a/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/gen/impl/DataNodeContainerSerializerSource.java +++ b/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/gen/impl/DataNodeContainerSerializerSource.java @@ -5,9 +5,9 @@ * 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.dom.codec.gen.impl; +import static com.google.common.base.Preconditions.checkState; import static java.util.Objects.requireNonNull; import java.util.HashMap; @@ -110,20 +110,8 @@ abstract class DataNodeContainerSerializerSource extends DataObjectSerializerSou if (!schemaChild.isAugmenting()) { final String getter = BindingSchemaMapping.getGetterMethodName(schemaChild); final Type childType = getterToType.get(getter); - if (childType == null) { - // FIXME AnyXml nodes are ignored, since their type cannot be found in generated bindnig - // Bug-706 https://bugs.opendaylight.org/show_bug.cgi?id=706 - if (schemaChild instanceof AnyXmlSchemaNode) { - LOG.warn("Node {} will be ignored. AnyXml is not yet supported from binding aware code." - + "Binding Independent code can be used to serialize anyXml nodes.", - schemaChild.getPath()); - continue; - } - - throw new IllegalStateException( - String.format("Unable to find type for child node %s. Expected child nodes: %s", - schemaChild.getPath(), getterToType)); - } + checkState(childType != null, "Unable to find type for child node %s. Expected child nodes: %s", + schemaChild.getPath(), getterToType); emitChild(sb, getter, childType, schemaChild); } } diff --git a/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/gen/impl/DataObjectSerializerSource.java b/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/gen/impl/DataObjectSerializerSource.java index 79d47c42eb..25ba79b376 100644 --- a/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/gen/impl/DataObjectSerializerSource.java +++ b/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/gen/impl/DataObjectSerializerSource.java @@ -66,7 +66,7 @@ abstract class DataObjectSerializerSource extends AbstractSource { return invoke(STREAM, "leafNode", escape(localName), value); } - protected static final CharSequence startLeafSet(final String localName,final CharSequence expected) { + protected static final CharSequence startLeafSet(final String localName, final CharSequence expected) { return invoke(STREAM, "startLeafSet", escape(localName), expected); } @@ -110,17 +110,17 @@ abstract class DataObjectSerializerSource extends AbstractSource { return invoke(STREAM, "startAugmentationNode", key); } - protected static final CharSequence startChoiceNode(final CharSequence localName,final CharSequence expected) { + protected static final CharSequence startChoiceNode(final CharSequence localName, final CharSequence expected) { return invoke(STREAM, "startChoiceNode", localName, expected); } - protected static final CharSequence startCaseNode(final CharSequence localName,final CharSequence expected) { + protected static final CharSequence startCaseNode(final CharSequence localName, final CharSequence expected) { return invoke(STREAM, "startCase", localName, expected); } - protected static final CharSequence anyxmlNode(final String name, final String value) + protected static final CharSequence anyxmlNode(final String localName, final CharSequence value) throws IllegalArgumentException { - return invoke(STREAM, "anyxmlNode", escape(name), name); + return invoke(STREAM, "anyxmlNode", escape(localName), value); } protected static final CharSequence endNode() { diff --git a/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/BindingCodecContext.java b/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/BindingCodecContext.java index 3ae940bc2b..83cc618092 100644 --- a/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/BindingCodecContext.java +++ b/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/BindingCodecContext.java @@ -47,9 +47,11 @@ import org.opendaylight.yangtools.yang.binding.Identifiable; import org.opendaylight.yangtools.yang.binding.Identifier; import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; import org.opendaylight.yangtools.yang.binding.Notification; +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.schema.stream.NormalizedNodeStreamWriter; +import org.opendaylight.yangtools.yang.model.api.AnyXmlSchemaNode; import org.opendaylight.yangtools.yang.model.api.DataNodeContainer; import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; import org.opendaylight.yangtools.yang.model.api.DocumentedNode.WithStatus; @@ -237,8 +239,9 @@ final class BindingCodecContext implements CodecContextFactory, BindingCodecTree final DataNodeContainer childSchema) { final Map getterToLeafSchema = new HashMap<>(); for (final DataSchemaNode leaf : childSchema.getChildNodes()) { - if (leaf instanceof TypedDataSchemaNode) { - getterToLeafSchema.put(BindingSchemaMapping.getGetterMethodName((TypedDataSchemaNode) leaf), leaf); + // FIXME: support AnyDataSchemaNode, too + if (leaf instanceof TypedDataSchemaNode || leaf instanceof AnyXmlSchemaNode) { + getterToLeafSchema.put(BindingSchemaMapping.getGetterMethodName(leaf), leaf); } } return getLeafNodesUsingReflection(parentClass, getterToLeafSchema); @@ -250,10 +253,6 @@ final class BindingCodecContext implements CodecContextFactory, BindingCodecTree for (final Method method : parentClass.getMethods()) { if (method.getParameterCount() == 0) { final DataSchemaNode schema = getterToLeafSchema.get(method.getName()); - if (!(schema instanceof TypedDataSchemaNode)) { - // We do not have schema for leaf, so we will ignore it (e.g. getClass). - continue; - } final ValueNodeCodecContext valueNode; if (schema instanceof LeafSchemaNode) { @@ -281,8 +280,15 @@ final class BindingCodecContext implements CodecContextFactory, BindingCodecTree final LeafListSchemaNode leafListSchema = (LeafListSchemaNode) schema; final Codec codec = getCodec(valueType, leafListSchema.getType()); valueNode = new LeafSetNodeCodecContext(leafListSchema, codec, method); + } else if (schema instanceof AnyXmlSchemaNode) { + final Class valueType = method.getReturnType(); + verify(OpaqueObject.class.isAssignableFrom(valueType), "Illegal value type %s", valueType); + valueNode = new OpaqueNodeCodecContext.AnyXml<>((AnyXmlSchemaNode) schema, method, + valueType.asSubclass(OpaqueObject.class)); } else { - throw new IllegalStateException("Unhandled typed schema " + schema); + verify(schema == null, "Unhandled schema %s for method %s", schema, method); + // We do not have schema for leaf, so we will ignore it (e.g. getClass). + continue; } leaves.put(schema.getQName().getLocalName(), valueNode); diff --git a/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/BindingNormalizedNodeCodecRegistry.java b/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/BindingNormalizedNodeCodecRegistry.java index c16280679a..3a94529223 100644 --- a/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/BindingNormalizedNodeCodecRegistry.java +++ b/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/BindingNormalizedNodeCodecRegistry.java @@ -133,6 +133,7 @@ public class BindingNormalizedNodeCodecRegistry implements DataObjectSerializerR } @Override + @SuppressFBWarnings("BC_UNCONFIRMED_CAST") public ContainerNode toNormalizedNodeRpcData(final DataContainer data) { // FIXME: Should the cast to DataObject be necessary? return serializeDataObject((DataObject) data, this::newRpcWriter); 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 6e3c7ee087..6a5d26ce4b 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 @@ -45,6 +45,7 @@ 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; @@ -122,7 +123,14 @@ abstract class DataObjectCodecContext, Method> childDataObj : clsToMethod.entrySet()) { final Method method = childDataObj.getValue(); verify(!method.isDefault(), "Unexpected default method %s in %s", method, bindingClass); - final DataContainerCodecPrototype childProto = loadChildPrototype(childDataObj.getKey()); + + 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); diff --git a/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/ForeignOpaqueData.java b/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/ForeignOpaqueData.java new file mode 100644 index 0000000000..5ba9b791dc --- /dev/null +++ b/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/ForeignOpaqueData.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2019 PANTHEON.tech, 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.dom.codec.impl; + +import static java.util.Objects.requireNonNull; + +import org.opendaylight.yangtools.yang.binding.AbstractOpaqueData; +import org.opendaylight.yangtools.yang.binding.OpaqueData; +import org.opendaylight.yangtools.yang.data.api.schema.ForeignDataNode; + +/** + * An {@link OpaqueData} implementation backed by {@link ForeignDataNode}. + * + * @param Object model type + */ +final class ForeignOpaqueData extends AbstractOpaqueData { + private final ForeignDataNode domData; + + ForeignOpaqueData(final ForeignDataNode domData) { + this.domData = requireNonNull(domData); + } + + @Override + public Class getObjectModel() { + return domData.getValueObjectModel(); + } + + @Override + public T getData() { + return domData.getValue(); + } + + ForeignDataNode domData() { + return domData; + } +} diff --git a/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/ForeignOpaqueObject.java b/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/ForeignOpaqueObject.java new file mode 100644 index 0000000000..9715f355e7 --- /dev/null +++ b/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/ForeignOpaqueObject.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2019 PANTHEON.tech, 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.dom.codec.impl; + +import static java.util.Objects.requireNonNull; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import org.eclipse.jdt.annotation.NonNull; +import org.opendaylight.mdsal.binding.spec.naming.BindingMapping; +import org.opendaylight.yangtools.yang.binding.AbstractOpaqueObject; +import org.opendaylight.yangtools.yang.binding.OpaqueData; +import org.opendaylight.yangtools.yang.binding.OpaqueObject; + +/** + * Proxy support for an OpaqueObject. + * + * @param OpaqueObject type + */ +// FIXME: MDSAL-442: this class has no run-time dependencies on other generated types, hence is a prime candidate for +// becoming a prototype instantiated in a separate ClassLoader (see MDSAL-401) and not being a proxy at all. +final class ForeignOpaqueObject> extends AbstractOpaqueObject + implements InvocationHandler { + private final @NonNull Class implementedInterface; + private final @NonNull ForeignOpaqueData value; + + ForeignOpaqueObject(final Class implementedInterface, final ForeignOpaqueData value) { + this.implementedInterface = requireNonNull(implementedInterface); + this.value = requireNonNull(value); + } + + @Override + public Class implementedInterface() { + return implementedInterface; + } + + @Override + public OpaqueData getValue() { + return value; + } + + @Override + public Object invoke(final Object proxy, final Method method, final Object[] args) { + switch (method.getName()) { + case "equals": + return equals(args[0]); + case "hashCode": + return hashCode(); + case "getValue": + return getValue(); + case "toString": + return toString(); + case BindingMapping.DATA_CONTAINER_IMPLEMENTED_INTERFACE_NAME: + return implementedInterface; + default: + throw new NoSuchMethodError("Unknown method " + method); + } + } +} diff --git a/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/LeafNodeCodecContext.java b/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/LeafNodeCodecContext.java index 249c49cbb5..e628568644 100644 --- a/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/LeafNodeCodecContext.java +++ b/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/LeafNodeCodecContext.java @@ -28,7 +28,7 @@ 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; -class LeafNodeCodecContext extends ValueNodeCodecContext { +class LeafNodeCodecContext extends ValueNodeCodecContext.WithCodec { static final class OfTypeObject extends LeafNodeCodecContext implements BindingTypeObjectCodecTreeNode { private final @NonNull Class bindingClass; diff --git a/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/LeafSetNodeCodecContext.java b/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/LeafSetNodeCodecContext.java index e9de07df61..4d92685dda 100644 --- a/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/LeafSetNodeCodecContext.java +++ b/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/LeafSetNodeCodecContext.java @@ -17,7 +17,7 @@ import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode; import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode; -final class LeafSetNodeCodecContext extends ValueNodeCodecContext { +final class LeafSetNodeCodecContext extends ValueNodeCodecContext.WithCodec { LeafSetNodeCodecContext(final LeafListSchemaNode schema, final Codec codec, final Method getter) { // FIXME: add support for defaults 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 new file mode 100644 index 0000000000..d14525c820 --- /dev/null +++ b/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/OpaqueNodeCodecContext.java @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2019 PANTHEON.tech, 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.dom.codec.impl; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Verify.verify; +import static java.util.Objects.requireNonNull; + +import com.google.common.base.Throwables; +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 javax.xml.transform.dom.DOMSource; +import org.eclipse.jdt.annotation.NonNull; +import org.opendaylight.mdsal.binding.dom.codec.api.BindingOpaqueObjectCodecTreeNode; +import org.opendaylight.yangtools.concepts.Codec; +import org.opendaylight.yangtools.yang.binding.OpaqueData; +import org.opendaylight.yangtools.yang.binding.OpaqueObject; +import org.opendaylight.yangtools.yang.data.api.schema.AnyXmlNode; +import org.opendaylight.yangtools.yang.data.api.schema.ForeignDataNode; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; +import org.opendaylight.yangtools.yang.data.impl.schema.Builders; +import org.opendaylight.yangtools.yang.model.api.AnyXmlSchemaNode; +import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; + +abstract class OpaqueNodeCodecContext> extends ValueNodeCodecContext + implements BindingOpaqueObjectCodecTreeNode { + static final class AnyXml> extends OpaqueNodeCodecContext { + AnyXml(final AnyXmlSchemaNode schema, final Method getter, final Class bindingClass) { + super(schema, getter, bindingClass); + } + + @Override + ForeignDataNode serializedData(final OpaqueData opaqueData) { + final Class model = opaqueData.getObjectModel(); + verify(DOMSource.class.isAssignableFrom(model), "Cannot just yet support object model %s", model); + return Builders.anyXmlBuilder().withNodeIdentifier(getDomPathArgument()) + .withValue((DOMSource) opaqueData.getData()).build(); + } + } + + private static final MethodType CONSTRUCTOR_TYPE = MethodType.methodType(void.class, InvocationHandler.class); + private static final MethodType OPAQUEOBJECT_TYPE = MethodType.methodType(OpaqueObject.class, + ForeignOpaqueObject.class); + + private final Codec valueCodec = new Codec() { + @Override + public Object serialize(final Object input) { + checkArgument(bindingClass.isInstance(input), "Unexpected input %s", input); + // FIXME: this works for DOMSource only, for generalization we need to pass down the object model, too + final OpaqueData opaqueData = bindingClass.cast(input).getValue(); + final Object data = opaqueData.getData(); + checkArgument(data instanceof DOMSource, "Unexpected data %s", data); + return data; + } + + @Override + public Object deserialize(final Object input) { + checkArgument(input instanceof NormalizedNode, "Unexpected input %s", input); + return OpaqueNodeCodecContext.this.deserializeObject((NormalizedNode) input); + } + }; + + private final MethodHandle proxyConstructor; + private final @NonNull Class bindingClass; + + OpaqueNodeCodecContext(final DataSchemaNode schema, final Method getter, final Class bindingClass) { + super(schema, getter, null); + this.bindingClass = requireNonNull(bindingClass); + + final Class proxyClass = Proxy.getProxyClass(bindingClass.getClassLoader(), bindingClass); + try { + proxyConstructor = MethodHandles.publicLookup().findConstructor(proxyClass, CONSTRUCTOR_TYPE) + .asType(OPAQUEOBJECT_TYPE); + } catch (NoSuchMethodException | IllegalAccessException e) { + throw new IllegalStateException("Failed to find contructor for class " + proxyClass, e); + } + } + + @Override + public final Class getBindingClass() { + return bindingClass; + } + + @Override + public final T deserialize(final NormalizedNode data) { + checkArgument(data instanceof ForeignDataNode, "Unexpected value %s", data); + final ForeignDataNode foreignData = (ForeignDataNode) data; + // Streaming cannot support anything but DOMSource-based AnyxmlNodes. + verify(foreignData instanceof AnyXmlNode, "Variable node %s not supported yet", foreignData); + + final ForeignOpaqueData opaqueData = new ForeignOpaqueData<>(foreignData); + return bindingClass.cast(createBindingProxy(new ForeignOpaqueObject<>(bindingClass, opaqueData))); + } + + @Override + public final ForeignDataNode serialize(final T data) { + final OpaqueData opaqueData = data.getValue(); + return opaqueData instanceof ForeignOpaqueData ? ((ForeignOpaqueData) opaqueData).domData() + : serializedData(opaqueData); + } + + @Override + protected final @NonNull Object deserializeObject(final NormalizedNode normalizedNode) { + return deserialize(normalizedNode); + } + + @Override + Codec getValueCodec() { + return valueCodec; + } + + abstract @NonNull ForeignDataNode serializedData(OpaqueData opaqueData); + + @SuppressWarnings("checkstyle:illegalCatch") + private OpaqueObject createBindingProxy(final ForeignOpaqueObject handler) { + try { + return (OpaqueObject) proxyConstructor.invokeExact(handler); + } catch (final Throwable e) { + Throwables.throwIfUnchecked(e); + throw new IllegalStateException(e); + } + } +} diff --git a/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/ValueNodeCodecContext.java b/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/ValueNodeCodecContext.java index a99ef85a93..d6baafe129 100644 --- a/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/ValueNodeCodecContext.java +++ b/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/ValueNodeCodecContext.java @@ -13,23 +13,35 @@ import java.lang.reflect.Method; import org.eclipse.jdt.annotation.NonNull; import org.opendaylight.yangtools.concepts.Codec; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier; -import org.opendaylight.yangtools.yang.model.api.TypedDataSchemaNode; +import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; /** * Abstract base class for atomic nodes. These are nodes which are not decomposed in the Binding Specification, such * as LeafNodes and LeafSetNodes. */ abstract class ValueNodeCodecContext extends NodeCodecContext implements NodeContextSupplier { + abstract static class WithCodec extends ValueNodeCodecContext { + private final @NonNull Codec valueCodec; + + WithCodec(final DataSchemaNode schema, final Codec codec, final Method getter, + final Object defaultObject) { + super(schema, getter, defaultObject); + this.valueCodec = requireNonNull(codec); + } + + @Override + final Codec getValueCodec() { + return valueCodec; + } + } + private final @NonNull NodeIdentifier yangIdentifier; - private final @NonNull Codec valueCodec; private final @NonNull Method getter; - private final @NonNull TypedDataSchemaNode schema; + private final @NonNull DataSchemaNode schema; private final Object defaultObject; - ValueNodeCodecContext(final TypedDataSchemaNode schema, final Codec codec, - final Method getter, final Object defaultObject) { + ValueNodeCodecContext(final DataSchemaNode schema, final Method getter, final Object defaultObject) { this.yangIdentifier = NodeIdentifier.create(schema.getQName()); - this.valueCodec = requireNonNull(codec); this.getter = requireNonNull(getter); this.schema = requireNonNull(schema); this.defaultObject = defaultObject; @@ -49,12 +61,10 @@ abstract class ValueNodeCodecContext extends NodeCodecContext implements NodeCon return getter; } - final Codec getValueCodec() { - return valueCodec; - } + abstract Codec getValueCodec(); @Override - public final TypedDataSchemaNode getSchema() { + public final DataSchemaNode getSchema() { return schema; } diff --git a/binding/mdsal-binding-dom-codec/src/test/java/org/opendaylight/mdsal/binding/dom/codec/test/AnyxmlLeafTest.java b/binding/mdsal-binding-dom-codec/src/test/java/org/opendaylight/mdsal/binding/dom/codec/test/AnyxmlLeafTest.java new file mode 100644 index 0000000000..72ae88f931 --- /dev/null +++ b/binding/mdsal-binding-dom-codec/src/test/java/org/opendaylight/mdsal/binding/dom/codec/test/AnyxmlLeafTest.java @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2019 PANTHEON.tech, 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.dom.codec.test; + +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertThat; + +import java.util.Map.Entry; +import javax.xml.transform.dom.DOMSource; +import org.junit.Test; +import org.opendaylight.yang.gen.v1.mdsal437.norev.Cont; +import org.opendaylight.yang.gen.v1.mdsal437.norev.ContBuilder; +import org.opendaylight.yang.gen.v1.mdsal437.norev.cont.ContAny; +import org.opendaylight.yangtools.util.xml.UntrustedXML; +import org.opendaylight.yangtools.yang.binding.AbstractOpaqueData; +import org.opendaylight.yangtools.yang.binding.AbstractOpaqueObject; +import org.opendaylight.yangtools.yang.binding.DataObject; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; +import org.opendaylight.yangtools.yang.binding.OpaqueData; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier; +import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; +import org.opendaylight.yangtools.yang.data.impl.schema.Builders; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +public class AnyxmlLeafTest extends AbstractBindingCodecTest { + private static final NodeIdentifier CONT_NODE_ID = new NodeIdentifier(Cont.QNAME); + + private DOMSource domSource; + private ContainerNode cont; + + @Override + public void before() { + super.before(); + + final Document doc = UntrustedXML.newDocumentBuilder().newDocument(); + final Element element = doc.createElement("foo"); + domSource = new DOMSource(element); + + cont = Builders.containerBuilder() + .withNodeIdentifier(CONT_NODE_ID) + .withChild(Builders.anyXmlBuilder() + .withNodeIdentifier(new NodeIdentifier(ContAny.QNAME)) + .withValue(domSource) + .build()) + .build(); + } + + @Test + public void testAnyxmlToBinding() { + final Entry, DataObject> entry = registry.fromNormalizedNode( + YangInstanceIdentifier.create(CONT_NODE_ID), cont); + assertEquals(InstanceIdentifier.create(Cont.class), entry.getKey()); + final DataObject ldo = entry.getValue(); + assertThat(ldo, instanceOf(Cont.class)); + + // So no... GrpAny should be null .. + final Cont contValue = (Cont) ldo; + assertNull(contValue.getGrpAny()); + + // ContAny is interesting + final ContAny anyCont = contValue.getContAny(); + assertNotNull(anyCont); + assertEquals(ContAny.class, anyCont.implementedInterface()); + + final OpaqueData value = anyCont.getValue(); + assertNotNull(value); + assertEquals(DOMSource.class, value.getObjectModel()); + assertSame(domSource, value.getData()); + + // Stable hashCode + final int hashOne = anyCont.hashCode(); + final int hashTwo = anyCont.hashCode(); + assertEquals(hashOne, hashTwo); + + // Basic equality + assertNotEquals(anyCont, null); + assertEquals(anyCont, anyCont); + assertEquals(new FakeCont(), anyCont); + assertEquals(anyCont, new FakeCont()); + assertNotEquals(anyCont, new TestNormalizedNodeCont()); + assertNotEquals(new TestNormalizedNodeCont(), anyCont); + } + + @Test + public void testAnyxmlFromBinding() { + final Entry> entry = registry.toNormalizedNode( + InstanceIdentifier.create(Cont.class), new ContBuilder().setContAny(new FakeCont()).build()); + assertEquals(YangInstanceIdentifier.create(CONT_NODE_ID), entry.getKey()); + assertEquals(cont, entry.getValue()); + } + + private final class FakeData extends AbstractOpaqueData { + @Override + public Class getObjectModel() { + return DOMSource.class; + } + + @Override + public DOMSource getData() { + return domSource; + } + } + + private abstract static class AbstractTestCont extends AbstractOpaqueObject implements ContAny { + + } + + private final class FakeCont extends AbstractTestCont { + @Override + public OpaqueData getValue() { + return new FakeData(); + } + } + + private final class TestNormalizedNodeCont extends AbstractTestCont { + @Override + public OpaqueData getValue() { + return new AbstractOpaqueData() { + + @Override + public Class getObjectModel() { + return NormalizedNode.class; + } + + @Override + public NormalizedNode getData() { + return cont; + } + }; + } + } +} -- 2.36.6