dbad1573d54a9ca83739eda9a5531c15fc211f6c
[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 com.google.common.collect.ImmutableList;
16 import java.io.IOException;
17 import java.lang.invoke.MethodHandle;
18 import java.lang.invoke.MethodHandles;
19 import java.lang.invoke.MethodType;
20 import java.lang.reflect.Constructor;
21 import java.lang.reflect.Method;
22 import javassist.CannotCompileException;
23 import javassist.CtClass;
24 import javassist.Modifier;
25 import javassist.NotFoundException;
26 import javax.xml.transform.dom.DOMSource;
27 import org.eclipse.jdt.annotation.NonNull;
28 import org.opendaylight.mdsal.binding.dom.codec.api.BindingOpaqueObjectCodecTreeNode;
29 import org.opendaylight.mdsal.binding.dom.codec.loader.CodecClassLoader;
30 import org.opendaylight.mdsal.binding.dom.codec.loader.StaticClassPool;
31 import org.opendaylight.yangtools.concepts.Codec;
32 import org.opendaylight.yangtools.yang.binding.OpaqueData;
33 import org.opendaylight.yangtools.yang.binding.OpaqueObject;
34 import org.opendaylight.yangtools.yang.data.api.schema.AnyXmlNode;
35 import org.opendaylight.yangtools.yang.data.api.schema.ForeignDataNode;
36 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
37 import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
38 import org.opendaylight.yangtools.yang.model.api.AnyXmlSchemaNode;
39 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
40
41 abstract class OpaqueNodeCodecContext<T extends OpaqueObject<T>> extends ValueNodeCodecContext
42         implements BindingOpaqueObjectCodecTreeNode<T> {
43     static final class AnyXml<T extends OpaqueObject<T>> extends OpaqueNodeCodecContext<T> {
44         AnyXml(final AnyXmlSchemaNode schema, final Method getter, final Class<T> bindingClass,
45                 final CodecClassLoader loader) {
46             super(schema, getter, bindingClass, loader);
47         }
48
49         @Override
50         ForeignDataNode<?, ?> serializedData(final OpaqueData<?> opaqueData) {
51             final Class<?> model = opaqueData.getObjectModel();
52             verify(DOMSource.class.isAssignableFrom(model), "Cannot just yet support object model %s", model);
53             return Builders.anyXmlBuilder().withNodeIdentifier(getDomPathArgument())
54                     .withValue((DOMSource) opaqueData.getData()).build();
55         }
56     }
57
58     private static final CtClass SUPERCLASS = StaticClassPool.findClass(CodecOpaqueObject.class);
59     private static final MethodType CONSTRUCTOR_TYPE = MethodType.methodType(OpaqueObject.class,
60         OpaqueData.class);
61
62     private final Codec<Object, Object> valueCodec = new Codec<Object, Object>() {
63         @Override
64         public Object serialize(final Object input) {
65             checkArgument(bindingClass.isInstance(input), "Unexpected input %s", input);
66             // FIXME: this works for DOMSource only, for generalization we need to pass down the object model, too
67             final OpaqueData<?> opaqueData = bindingClass.cast(input).getValue();
68             final Object data = opaqueData.getData();
69             checkArgument(data instanceof DOMSource, "Unexpected data %s", data);
70             return data;
71         }
72
73         @Override
74         public Object deserialize(final Object input) {
75             checkArgument(input instanceof NormalizedNode, "Unexpected input %s", input);
76             return OpaqueNodeCodecContext.this.deserializeObject((NormalizedNode<?, ?>) input);
77         }
78     };
79
80     private final MethodHandle proxyConstructor;
81     private final @NonNull Class<T> bindingClass;
82
83     OpaqueNodeCodecContext(final DataSchemaNode schema, final Method getter, final Class<T> bindingClass,
84             final CodecClassLoader loader) {
85         super(schema, getter, null);
86         this.bindingClass = requireNonNull(bindingClass);
87         proxyConstructor = createImpl(loader, bindingClass);
88     }
89
90     @Override
91     public final Class<T> getBindingClass() {
92         return bindingClass;
93     }
94
95     @Override
96     public final T deserialize(final NormalizedNode<?, ?> data) {
97         checkArgument(data instanceof ForeignDataNode, "Unexpected value %s", data);
98         final ForeignDataNode<?, ?> foreignData = (ForeignDataNode<?, ?>) data;
99         // Streaming cannot support anything but DOMSource-based AnyxmlNodes.
100         verify(foreignData instanceof AnyXmlNode, "Variable node %s not supported yet", foreignData);
101
102         return bindingClass.cast(createBindingProxy(new ForeignOpaqueData<>(foreignData)));
103     }
104
105     @Override
106     public final ForeignDataNode<?, ?> serialize(final T data) {
107         final OpaqueData<?> opaqueData = data.getValue();
108         return opaqueData instanceof ForeignOpaqueData ? ((ForeignOpaqueData<?>) opaqueData).domData()
109                 : serializedData(opaqueData);
110     }
111
112     @Override
113     protected final @NonNull Object deserializeObject(final NormalizedNode<?, ?> normalizedNode) {
114         return deserialize(normalizedNode);
115     }
116
117     @Override
118     Codec<Object, Object> getValueCodec() {
119         return valueCodec;
120     }
121
122     abstract @NonNull ForeignDataNode<?, ?> serializedData(OpaqueData<?> opaqueData);
123
124     @SuppressWarnings("checkstyle:illegalCatch")
125     private OpaqueObject<?> createBindingProxy(final OpaqueData<?> data) {
126         try {
127             return (OpaqueObject<?>) proxyConstructor.invokeExact(data);
128         } catch (final Throwable e) {
129             Throwables.throwIfUnchecked(e);
130             throw new IllegalStateException(e);
131         }
132     }
133
134     private static MethodHandle createImpl(final CodecClassLoader rootLoader, final Class<?> bindingClass) {
135         final Class<?> proxyClass;
136         try {
137             proxyClass = rootLoader.generateSubclass(SUPERCLASS, bindingClass, "codecImpl",
138                 (pool, binding, generated) -> {
139                     generated.addInterface(binding);
140                     generated.setModifiers(Modifier.PUBLIC | Modifier.FINAL);
141                     return ImmutableList.of();
142                 });
143         } catch (CannotCompileException | IOException | NotFoundException e) {
144             throw new LinkageError("Failed to instantiate prototype for " + bindingClass, e);
145         }
146
147         Constructor<?> ctor;
148         try {
149             ctor = proxyClass.getDeclaredConstructor(OpaqueData.class);
150         } catch (NoSuchMethodException e) {
151             throw new LinkageError("Failed to acquire constructor for prototype " + proxyClass, e);
152         }
153         try {
154             return MethodHandles.publicLookup().unreflectConstructor(ctor).asType(CONSTRUCTOR_TYPE);
155         } catch (IllegalAccessException e) {
156             throw new LinkageError("Failed to access constructor for prototype " + proxyClass, e);
157         }
158     }
159 }