Bug 1443: Implemented Lazy deserialization using dynamic proxies
[mdsal.git] / code-generator / binding-data-codec / src / main / java / org / opendaylight / yangtools / binding / data / codec / gen / impl / AbstractStreamWriterGenerator.java
1 /*
2  * Copyright (c) 2014 Cisco Systems, Inc. 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.yangtools.binding.data.codec.gen.impl;
9
10 import com.google.common.base.Preconditions;
11 import com.google.common.base.Supplier;
12 import com.google.common.cache.CacheBuilder;
13 import com.google.common.cache.CacheLoader;
14 import com.google.common.cache.LoadingCache;
15
16 import java.lang.reflect.Field;
17 import java.lang.reflect.InvocationTargetException;
18 import java.util.Map.Entry;
19
20 import javassist.CannotCompileException;
21 import javassist.CtClass;
22 import javassist.CtField;
23 import javassist.CtMethod;
24 import javassist.Modifier;
25 import javassist.NotFoundException;
26
27 import org.opendaylight.yangtools.binding.data.codec.gen.spi.StaticConstantDefinition;
28 import org.opendaylight.yangtools.binding.data.codec.util.AugmentableDispatchSerializer;
29 import org.opendaylight.yangtools.binding.generator.util.Types;
30 import org.opendaylight.yangtools.sal.binding.generator.util.BindingRuntimeContext;
31 import org.opendaylight.yangtools.sal.binding.generator.util.ClassCustomizer;
32 import org.opendaylight.yangtools.sal.binding.generator.util.JavassistUtils;
33 import org.opendaylight.yangtools.sal.binding.model.api.GeneratedType;
34 import org.opendaylight.yangtools.util.ClassLoaderUtils;
35 import org.opendaylight.yangtools.yang.binding.BindingStreamEventWriter;
36 import org.opendaylight.yangtools.yang.binding.DataContainer;
37 import org.opendaylight.yangtools.yang.binding.DataObject;
38 import org.opendaylight.yangtools.yang.binding.DataObjectSerializerImplementation;
39 import org.opendaylight.yangtools.yang.binding.DataObjectSerializerRegistry;
40 import org.opendaylight.yangtools.yang.binding.util.BindingReflections;
41 import org.opendaylight.yangtools.yang.model.api.AugmentationSchema;
42 import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode;
43 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
44 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
45 import org.slf4j.Logger;
46 import org.slf4j.LoggerFactory;
47
48 abstract class AbstractStreamWriterGenerator extends AbstractGenerator implements DataObjectSerializerGenerator {
49     private static final Logger LOG = LoggerFactory.getLogger(AbstractStreamWriterGenerator.class);
50
51     protected static final String SERIALIZE_METHOD_NAME = "serialize";
52     protected static final AugmentableDispatchSerializer AUGMENTABLE = new AugmentableDispatchSerializer();
53     private static final Field FIELD_MODIFIERS;
54
55     private final LoadingCache<Class<?>, DataObjectSerializerImplementation> implementations;
56     private final CtClass[] serializeArguments;
57     private final JavassistUtils javassist;
58     private BindingRuntimeContext context;
59
60     static {
61         /*
62          * Cache reflection access to field modifiers field. We need this to set
63          * fix the static declared fields to final once we initialize them. If we
64          * cannot get access, that's fine, too.
65          */
66         Field f = null;
67         try {
68             f = Field.class.getDeclaredField("modifiers");
69             f.setAccessible(true);
70         } catch (NoSuchFieldException | SecurityException e) {
71             LOG.warn("Could not get Field modifiers field, serializers run at decreased efficiency", e);
72         }
73
74         FIELD_MODIFIERS = f;
75     }
76
77     protected AbstractStreamWriterGenerator(final JavassistUtils utils) {
78         super();
79         this.javassist = Preconditions.checkNotNull(utils,"JavassistUtils instance is required.");
80         this.serializeArguments = new CtClass[] {
81                 javassist.asCtClass(DataObjectSerializerRegistry.class),
82                 javassist.asCtClass(DataObject.class),
83                 javassist.asCtClass(BindingStreamEventWriter.class),
84         };
85         javassist.appendClassLoaderIfMissing(DataObjectSerializerPrototype.class.getClassLoader());
86         this.implementations = CacheBuilder.newBuilder().weakKeys().build(new SerializerImplementationLoader());
87     }
88
89     @Override
90     public final DataObjectSerializerImplementation getSerializer(final Class<?> type) {
91         return implementations.getUnchecked(type);
92     }
93
94     @Override
95     public final void onBindingRuntimeContextUpdated(final BindingRuntimeContext runtime) {
96         this.context = runtime;
97     }
98
99     @Override
100     protected final String loadSerializerFor(final Class<?> cls) {
101         return implementations.getUnchecked(cls).getClass().getName();
102     }
103
104     private final class SerializerImplementationLoader extends CacheLoader<Class<?>, DataObjectSerializerImplementation> {
105
106         private static final String GETINSTANCE_METHOD_NAME = "getInstance";
107         private static final String SERIALIZER_SUFFIX = "$StreamWriter";
108
109         private String getSerializerName(final Class<?> type) {
110             return type.getName() + SERIALIZER_SUFFIX;
111         }
112
113         @Override
114         @SuppressWarnings("unchecked")
115         public DataObjectSerializerImplementation load(final Class<?> type) throws Exception {
116             Preconditions.checkArgument(BindingReflections.isBindingClass(type));
117             Preconditions.checkArgument(DataContainer.class.isAssignableFrom(type),"DataContainer is not assingnable from %s from classloader %s.",type,type.getClassLoader());
118
119             final String serializerName = getSerializerName(type);
120
121             Class<? extends DataObjectSerializerImplementation> cls;
122             try {
123                 cls = (Class<? extends DataObjectSerializerImplementation>) ClassLoaderUtils
124                         .loadClass(type.getClassLoader(), serializerName);
125             } catch (ClassNotFoundException e) {
126                 cls = generateSerializer(type, serializerName);
127             }
128
129             final DataObjectSerializerImplementation obj =
130                     (DataObjectSerializerImplementation) cls.getDeclaredMethod(GETINSTANCE_METHOD_NAME).invoke(null);
131             LOG.debug("Loaded serializer {} for class {}", obj, type);
132             return obj;
133         }
134
135         private Class<? extends DataObjectSerializerImplementation> generateSerializer(final Class<?> type,
136                 final String serializerName) throws CannotCompileException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException, NoSuchFieldException {
137             final DataObjectSerializerSource source = generateEmitterSource(type, serializerName);
138             final CtClass poolClass = generateEmitter0(type, source, serializerName);
139             @SuppressWarnings("unchecked")
140             final Class<? extends DataObjectSerializerImplementation> cls = poolClass.toClass(type.getClassLoader(), type.getProtectionDomain());
141
142             /*
143              * Due to OSGi class loader rules we cannot initialize the fields during
144              * construction, as the initializer expressions do not see our implementation
145              * classes. This should be almost as good as that, as we are resetting the
146              * fields to final before ever leaking the class.
147              */
148             for (StaticConstantDefinition constant : source.getStaticConstants()) {
149                 final Field field = cls.getDeclaredField(constant.getName());
150                 field.setAccessible(true);
151                 field.set(null, constant.getValue());
152
153                 if (FIELD_MODIFIERS != null) {
154                     FIELD_MODIFIERS.setInt(field, field.getModifiers() | Modifier.FINAL);
155                 }
156             }
157
158             return cls;
159         }
160     }
161
162     private DataObjectSerializerSource generateEmitterSource(final Class<?> type, final String serializerName) {
163         Types.typeForClass(type);
164         javassist.appendClassLoaderIfMissing(type.getClassLoader());
165         Entry<GeneratedType, Object> typeWithSchema = context.getTypeWithSchema(type);
166         GeneratedType generatedType = typeWithSchema.getKey();
167         Object schema = typeWithSchema.getValue();
168
169         final DataObjectSerializerSource source;
170         if (schema instanceof ContainerSchemaNode) {
171             source = generateContainerSerializer(generatedType, (ContainerSchemaNode) schema);
172         } else if (schema instanceof ListSchemaNode){
173             ListSchemaNode casted = (ListSchemaNode) schema;
174             if (casted.getKeyDefinition().isEmpty()) {
175                 source = generateUnkeyedListEntrySerializer(generatedType, casted);
176             } else {
177                 source = generateMapEntrySerializer(generatedType, casted);
178             }
179         } else if(schema instanceof AugmentationSchema) {
180             source = generateSerializer(generatedType,(AugmentationSchema) schema);
181         } else if(schema instanceof ChoiceCaseNode) {
182             source = generateCaseSerializer(generatedType,(ChoiceCaseNode) schema);
183         } else {
184             throw new UnsupportedOperationException("Schema type " + schema.getClass() + " is not supported");
185         }
186         return source;
187     }
188
189     private CtClass generateEmitter0(final Class<?> type, final DataObjectSerializerSource source, final String serializerName) {
190         final CtClass product;
191         try {
192             product = javassist.instantiatePrototype(DataObjectSerializerPrototype.class.getName(), serializerName, new ClassCustomizer() {
193                 @Override
194                 public void customizeClass(final CtClass cls) throws CannotCompileException, NotFoundException {
195                     /* getSerializerBody() has side effects, such as loading classes
196                      * and codecs, it should be run in model class loader in order to
197                      * correctly reference load child classes
198                      */
199                     final String body = ClassLoaderUtils.withClassLoader(type.getClassLoader(), new Supplier<String>() {
200                             @Override
201                             public String get() {
202                                 return source.getSerializerBody().toString();
203                             }
204                         }
205                     );
206
207                     // Generate any static fields
208                     for (StaticConstantDefinition def : source.getStaticConstants()) {
209                         CtField field = new CtField(javassist.asCtClass(def.getType()), def.getName(), cls);
210                         field.setModifiers(Modifier.PRIVATE + Modifier.STATIC);
211                         cls.addField(field);
212                     }
213
214                     // Replace serialize() -- may reference static fields
215                     final CtMethod serializeTo = cls.getDeclaredMethod(SERIALIZE_METHOD_NAME, serializeArguments);
216                     serializeTo.setBody(body);
217
218                     // The prototype is not visible, so we need to take care of that
219                     cls.setModifiers(Modifier.setPublic(cls.getModifiers()));
220                 }
221             });
222         } catch (NotFoundException e) {
223             LOG.error("Failed to instatiate serializer {}", source, e);
224             throw new LinkageError("Unexpected instantation problem: serializer prototype not found", e);
225         }
226         return product;
227     }
228
229     /**
230      * Generates serializer source code for supplied container node,
231      * which will read supplied binding type and invoke proper methods
232      * on supplied {@link BindingStreamEventWriter}.
233      * <p>
234      * Implementation is required to recursively invoke events
235      * for all reachable binding objects.
236      *
237      * @param type Binding type of container
238      * @param node Schema of container
239      * @return Source for container node writer
240      */
241     protected abstract DataObjectSerializerSource generateContainerSerializer(GeneratedType type, ContainerSchemaNode node);
242
243     /**
244      * Generates serializer source for supplied case node,
245      * which will read supplied binding type and invoke proper methods
246      * on supplied {@link BindingStreamEventWriter}.
247      * <p>
248      * Implementation is required to recursively invoke events
249      * for all reachable binding objects.
250      *
251      * @param type Binding type of case
252      * @param node Schema of case
253      * @return Source for case node writer
254      */
255     protected abstract DataObjectSerializerSource generateCaseSerializer(GeneratedType type, ChoiceCaseNode node);
256
257     /**
258      * Generates serializer source for supplied list node,
259      * which will read supplied binding type and invoke proper methods
260      * on supplied {@link BindingStreamEventWriter}.
261      * <p>
262      * Implementation is required to recursively invoke events
263      * for all reachable binding objects.
264      *
265      * @param type Binding type of list
266      * @param node Schema of list
267      * @return Source for list node writer
268      */
269     protected abstract DataObjectSerializerSource generateMapEntrySerializer(GeneratedType type, ListSchemaNode node);
270
271     /**
272      * Generates serializer source for supplied list node,
273      * which will read supplied binding type and invoke proper methods
274      * on supplied {@link BindingStreamEventWriter}.
275      * <p>
276      * Implementation is required to recursively invoke events
277      * for all reachable binding objects.
278      *
279      * @param type Binding type of list
280      * @param node Schema of list
281      * @return Source for list node writer
282      */
283     protected abstract DataObjectSerializerSource generateUnkeyedListEntrySerializer(GeneratedType type, ListSchemaNode node);
284
285     /**
286      * Generates serializer source for supplied augmentation node,
287      * which will read supplied binding type and invoke proper methods
288      * on supplied {@link BindingStreamEventWriter}.
289      * <p>
290      * Implementation is required to recursively invoke events
291      * for all reachable binding objects.
292      *
293      * @param type Binding type of augmentation
294      * @param node Schema of augmentation
295      * @return Source for augmentation node writer
296      */
297     protected abstract DataObjectSerializerSource generateSerializer(GeneratedType type, AugmentationSchema schema);
298
299 }