Add codec support for anyxml classes
[mdsal.git] / binding / mdsal-binding-dom-codec / src / main / java / org / opendaylight / mdsal / binding / dom / codec / impl / OpaqueNodeCodecContext.java
1 /*
2  * Copyright (c) 2019 PANTHEON.tech, s.r.o. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.mdsal.binding.dom.codec.impl;
9
10 import static com.google.common.base.Preconditions.checkArgument;
11 import static com.google.common.base.Verify.verify;
12 import static java.util.Objects.requireNonNull;
13
14 import com.google.common.base.Throwables;
15 import java.lang.invoke.MethodHandle;
16 import java.lang.invoke.MethodHandles;
17 import java.lang.invoke.MethodType;
18 import java.lang.reflect.InvocationHandler;
19 import java.lang.reflect.Method;
20 import java.lang.reflect.Proxy;
21 import javax.xml.transform.dom.DOMSource;
22 import org.eclipse.jdt.annotation.NonNull;
23 import org.opendaylight.mdsal.binding.dom.codec.api.BindingOpaqueObjectCodecTreeNode;
24 import org.opendaylight.yangtools.concepts.Codec;
25 import org.opendaylight.yangtools.yang.binding.OpaqueData;
26 import org.opendaylight.yangtools.yang.binding.OpaqueObject;
27 import org.opendaylight.yangtools.yang.data.api.schema.AnyXmlNode;
28 import org.opendaylight.yangtools.yang.data.api.schema.ForeignDataNode;
29 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
30 import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
31 import org.opendaylight.yangtools.yang.model.api.AnyXmlSchemaNode;
32 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
33
34 abstract class OpaqueNodeCodecContext<T extends OpaqueObject<T>> extends ValueNodeCodecContext
35         implements BindingOpaqueObjectCodecTreeNode<T> {
36     static final class AnyXml<T extends OpaqueObject<T>> extends OpaqueNodeCodecContext<T> {
37         AnyXml(final AnyXmlSchemaNode schema, final Method getter, final Class<T> bindingClass) {
38             super(schema, getter, bindingClass);
39         }
40
41         @Override
42         ForeignDataNode<?, ?> serializedData(final OpaqueData<?> opaqueData) {
43             final Class<?> model = opaqueData.getObjectModel();
44             verify(DOMSource.class.isAssignableFrom(model), "Cannot just yet support object model %s", model);
45             return Builders.anyXmlBuilder().withNodeIdentifier(getDomPathArgument())
46                     .withValue((DOMSource) opaqueData.getData()).build();
47         }
48     }
49
50     private static final MethodType CONSTRUCTOR_TYPE = MethodType.methodType(void.class, InvocationHandler.class);
51     private static final MethodType OPAQUEOBJECT_TYPE = MethodType.methodType(OpaqueObject.class,
52         ForeignOpaqueObject.class);
53
54     private final Codec<Object, Object> valueCodec = new Codec<Object, Object>() {
55         @Override
56         public Object serialize(final Object input) {
57             checkArgument(bindingClass.isInstance(input), "Unexpected input %s", input);
58             // FIXME: this works for DOMSource only, for generalization we need to pass down the object model, too
59             final OpaqueData<?> opaqueData = bindingClass.cast(input).getValue();
60             final Object data = opaqueData.getData();
61             checkArgument(data instanceof DOMSource, "Unexpected data %s", data);
62             return data;
63         }
64
65         @Override
66         public Object deserialize(final Object input) {
67             checkArgument(input instanceof NormalizedNode, "Unexpected input %s", input);
68             return OpaqueNodeCodecContext.this.deserializeObject((NormalizedNode<?, ?>) input);
69         }
70     };
71
72     private final MethodHandle proxyConstructor;
73     private final @NonNull Class<T> bindingClass;
74
75     OpaqueNodeCodecContext(final DataSchemaNode schema, final Method getter, final Class<T> bindingClass) {
76         super(schema, getter, null);
77         this.bindingClass = requireNonNull(bindingClass);
78
79         final Class<?> proxyClass = Proxy.getProxyClass(bindingClass.getClassLoader(), bindingClass);
80         try {
81             proxyConstructor = MethodHandles.publicLookup().findConstructor(proxyClass, CONSTRUCTOR_TYPE)
82                     .asType(OPAQUEOBJECT_TYPE);
83         } catch (NoSuchMethodException | IllegalAccessException e) {
84             throw new IllegalStateException("Failed to find contructor for class " + proxyClass, e);
85         }
86     }
87
88     @Override
89     public final Class<T> getBindingClass() {
90         return bindingClass;
91     }
92
93     @Override
94     public final T deserialize(final NormalizedNode<?, ?> data) {
95         checkArgument(data instanceof ForeignDataNode, "Unexpected value %s", data);
96         final ForeignDataNode<?, ?> foreignData = (ForeignDataNode<?, ?>) data;
97         // Streaming cannot support anything but DOMSource-based AnyxmlNodes.
98         verify(foreignData instanceof AnyXmlNode, "Variable node %s not supported yet", foreignData);
99
100         final ForeignOpaqueData<?> opaqueData = new ForeignOpaqueData<>(foreignData);
101         return bindingClass.cast(createBindingProxy(new ForeignOpaqueObject<>(bindingClass, opaqueData)));
102     }
103
104     @Override
105     public final ForeignDataNode<?, ?> serialize(final T data) {
106         final OpaqueData<?> opaqueData = data.getValue();
107         return opaqueData instanceof ForeignOpaqueData ? ((ForeignOpaqueData<?>) opaqueData).domData()
108                 : serializedData(opaqueData);
109     }
110
111     @Override
112     protected final @NonNull Object deserializeObject(final NormalizedNode<?, ?> normalizedNode) {
113         return deserialize(normalizedNode);
114     }
115
116     @Override
117     Codec<Object, Object> getValueCodec() {
118         return valueCodec;
119     }
120
121     abstract @NonNull ForeignDataNode<?, ?> serializedData(OpaqueData<?> opaqueData);
122
123     @SuppressWarnings("checkstyle:illegalCatch")
124     private OpaqueObject<?> createBindingProxy(final ForeignOpaqueObject<?> handler) {
125         try {
126             return (OpaqueObject<?>) proxyConstructor.invokeExact(handler);
127         } catch (final Throwable e) {
128             Throwables.throwIfUnchecked(e);
129             throw new IllegalStateException(e);
130         }
131     }
132 }